نگارش اصلی

کتابخانه media3-ui-compose مؤلفه‌های بنیادی را برای ساختن میانای کاربر رسانه در Jetpack Compose ارائه می‌دهد. این API برای توسعه‌دهندگانی طراحی شده است که به سفارشی‌سازی بیشتری نسبت به آنچه کتابخانه media3-ui-compose-material3 ارائه می‌دهد نیاز دارند. این صفحه نحوه استفاده از مؤلفه‌های اصلی و نگه‌دارنده‌های حالت را برای ایجاد میانای کاربر سفارشی پخش‌کننده رسانه توضیح می‌دهد.

ترکیب کردن Material3 و مؤلفه‌های Compose سفارشی

کتابخانه media3-ui-compose-material3 به‌گونه‌ای طراحی شده است که انعطاف‌پذیر باشد. می‌توانید از مؤلفه‌های ازپیش‌ساخته‌شده برای بیشتر واسط کاربر خود استفاده کنید، اما وقتی به کنترل بیشتری نیاز دارید، یک مؤلفه را با پیاده‌سازی سفارشی جایگزین کنید. اینجا است که کتابخانه media3-ui-compose وارد بازی می‌شود.

برای مثال، تصور کنید می‌خواهید از PreviousButton و NextButton استاندارد از کتابخانه Material3 استفاده کنید، اما به PlayPauseButton کاملاً سفارشی نیاز دارید. می‌توانید بااستفاده از PlayPauseButton از کتابخانه اصلی media3-ui-compose به این هدف برسید و آن را درکنار مؤلفه‌های ازپیش‌ساخته قرار دهید.

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 یک محفظه حالت برای یک composable که پیشرفت پخش‌کننده را نمایش می‌دهد.
ContentFrame سطحی برای نمایش محتوای رسانه‌ای که مدیریت نسبت ابعادی، تغییر اندازه، و شاتر را انجام می‌دهد
PlayerSurface سطح خامی که SurfaceView و TextureView را در AndroidView می‌پیچد.

سفارشی‌سازی کردن ترکیب‌پذیر Player

هنگام استفاده از کتابخانه media3-ui-compose-material3، Player composable تجربه کامل بازپخش رسانه را ارائه می‌دهد. می‌توانید چیدمان آن را با ارائه ترکیب‌پذیرهای خود به جایگاه‌های محتوای آن سفارشی کنید، یا می‌توانید از شیء PlayerDefaults برای سفارشی‌سازی بخش‌های خاصی از واسط کاربر، مانند TopControls، CenterControls، BottomControls، و ErrorOverlay استفاده کنید.

شکل ۱ نشان می‌دهد که ترکیب‌پذیر Player جایگاه‌های خود را کجا قرار می‌دهد. شکل ۲ ترتیب ترسیم لایه‌ها را نشان می‌دهد.

پخش‌کننده ویدیو با خطوط تیره که جایگاه‌های آن را مشخص می‌کند. از بالا به پایین: topControls در امتداد لبه بالایی، errorOverlay بالای مرکز، centerControls در وسط با دکمه‌های backSecondary،‏ back،‏ central،‏ forward، و forwardSecondary، زیرنویس‌ها نزدیک به پایین، و bottomControls در امتداد لبه پایینی با جایگاه‌های above،‏ left،‏ progressSlider،‏ right، و below. ContentFrame فضای پشت آن‌ها را پر می‌کند.
شکل ۱. جایگاه‌های تابع ترکیب‌پذیر Player. کنترل‌های وسط و پایین، جایگاه‌های PlayerDefaults.CenterControls و PlayerDefaults.BottomControls را نشان می‌دهند. جایگاه‌های topControls، above، و below به‌طور پیش‌فرض خالی هستند.
نمای انفجاری از شش لایه پشته‌ای. از پایین به بالا: ۱،
      PlayerSurface؛ ۲، شاتر؛ ۳، کار هنری؛ ۴، زیرنویس؛ ۵، errorOverlay؛ و
      ۶، topControls،‏ centerControls، و bottomControls. لایه‌های ۱ تا ۴ ContentFrame را تشکیل می‌دهند و هر شش لایه Player را تشکیل می‌دهند.
شکل ۲. لایه‌های Player composable، به‌ترتیب ترسیم شماره‌گذاری شده‌اند.

برای مثال، می‌توانید از PlayerDefaults.CenterControls برای لغو کردن فقط دکمه مرکزی پخش/مکث استفاده کنید و بقیه کنترل‌های مرکزی را دست‌نخورده باقی بگذارید. همچنین می‌توانید یک composable کاملاً سفارشی را برای پارامتری مانند topControls ارسال کنید، و موارد دیگر مانند bottomControls را کاملاً حذف کنید تا در حالت پیش‌فرض خود باقی بمانند.

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

نگهدارنده‌های حالت واسط کاربر

اگر هیچ‌یک از مؤلفه‌های داربست نیازهای شما را برآورده نمی‌کند، می‌توانید مستقیماً از اشیاء وضعیت استفاده کنید. به‌طورکلی توصیه می‌شود از روش‌های remember مربوطه برای حفظ ظاهر میانای کاربر بین بازترکیب‌ها استفاده کنید.

برای درک بهتر نحوه استفاده از انعطاف‌پذیری نگه‌دارنده‌های حالت میانای کاربر درمقابل ترکیب‌پذیرها، درباره نحوه مدیریت حالت توسط Compose مطالعه کنید.

نگهدارنده‌های حالت دکمه

برای برخی‌از حالت‌های میانای کاربر، کتابخانه فرض می‌کند که احتمالاً توسط ترکیب‌پذیرهای دکمه‌مانند مصرف خواهند شد.

ایالت remember*State نوع
PlayPauseButtonState rememberPlayPauseButtonState تغییر وضعیت ۲
PreviousButtonState rememberPreviousButtonState ثابت
NextButtonState rememberNextButtonState ثابت
RepeatButtonState rememberRepeatButtonState تغییر وضعیت ۳
ShuffleButtonState rememberShuffleButtonState تغییر وضعیت ۲
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),
  )
}

نگهدارنده‌های حالت برونداد دیداری

CurrentMediaItemState اطلاعاتی درباره MediaItem درحال پخش ارائه می‌دهد، درحالی‌که PlaylistState اطلاعاتی درباره MediaItems تنظیم‌شده در پخش‌کننده ارائه می‌دهد. این‌ها برای نمایش اطلاعات فراداده در واسط کاربر سفارشی شما مفید هستند.

ErrorState اطلاعاتی درباره وضعیت خطای فعلی پخش‌کننده ارائه می‌دهد، که می‌توان از آن برای نمایش هم‌پوشانی خطا استفاده کرد.

PresentationState اطلاعات مربوط به زمانی را که خروجی ویدیو در PlayerSurface می‌تواند نشان داده شود یا باید با عنصر میانای کاربر جای‌بان پوشانده شود نگه می‌دارد. ContentFrame ترکیب‌پذیر، مدیریت نسبت ابعادی را با مراقبت از نمایش شاتر روی سطحی که هنوز آماده نیست ترکیب می‌کند.

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

در اینجا، می‌توانیم از هر دو presentationState.videoAspectRatio برای مقیاس‌بندی «سطح» به نسبت ابعادی انتخاب‌شده (برای انواع بیشتر، به مقیاس محتوا مراجعه کنید) و presentationState.coverSurface برای دانستن اینکه چه زمانی برای نمایش «سطح» مناسب نیست استفاده کنیم. در این حالت، می‌توانید یک شاتر مات را در بالای سطح قرار دهید که وقتی سطح آماده شد، ناپدید می‌شود. ContentFrame به شما امکان می‌دهد شاتر را به‌عنوان یک لامبدا دنباله‌دار سفارشی‌سازی کنید، اما به‌طور پیش‌فرض، یک @Composable Box سیاه خواهد بود که اندازه ظرف والد را پر می‌کند.

‫Flows کجا است؟

بسیاری از توسعه‌دهندگان 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(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)
    }
  }
}

برای واکنش نشان دادن به Player.Events خودتان، می‌توانید آن‌ها را بااستفاده از Player.listen بگیرید که یک suspend fun است و به شما امکان می‌دهد وارد دنیای کوروتین شوید و به‌طور نامحدود به Player.Events گوش دهید. پیاده‌سازی Media3 از حالت‌های مختلف واسط کاربر به توسعه‌دهنده نهایی کمک می‌کند تا نگران یادگیری درباره Player.Events نباشد.