کتابخانه 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 جایگاههای خود را کجا قرار میدهد. شکل ۲ ترتیب ترسیم لایهها را نشان میدهد.
Player.
کنترلهای وسط و پایین، جایگاههای
PlayerDefaults.CenterControls و
PlayerDefaults.BottomControls را نشان میدهند. جایگاههای topControls،
above، و below بهطور پیشفرض
خالی هستند.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میتواند واسط کاربر را به حالتهای بالقوه غیرقانونی هدایت کند.
ایجاد حالتهای واسط کاربر سفارشی
اگر حالتهای واسط کاربر موجود نیازهایتان را برآورده نمیکند، میتوانید حالتهای واسط کاربر سفارشی اضافه کنید. کد منبع وضعیت موجود را بررسی کنید تا الگو را کپی کنید. یک کلاس نگهدارنده حالت واسط کاربر معمولی کارهای زیر را انجام میدهد:
- شیء
Playerرا میگیرد. - بااستفاده از کوروتینها در
Playerمشترک میشود. برای جزئیات بیشتر، بهPlayer.listenمراجعه کنید. - با بهروزرسانی وضعیت داخلی خود به
Player.Eventsخاصی پاسخ میدهد. - فرمانهای منطق کسبوکار را میپذیرد که به بهروزرسانی مناسب
Playerتبدیل خواهد شد. - میتواند در چندین مکان در سراسر درخت میانای کاربر ایجاد شود و همیشه نمای ثابتی از وضعیت «بازیکن» را حفظ خواهد کرد.
- فیلدهای Compose
Stateرا که میتوانند توسط یک composable برای پاسخ پویا به تغییرات مصرف شوند، نمایش میدهد. - با تابع
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 نباشد.