کتابخانه 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 |
Raw surface which wraps SurfaceView and TextureView in AndroidView. |
Artwork |
یک composable که اثر هنری فایل رسانهای فعلی، مانند جلد آلبوم، را نمایش میدهد. |
سفارشیسازی کردن ترکیبپذیر Player
هنگام استفاده از کتابخانه media3-ui-compose-material3،
Player composable تجربه کاملی از بازپخش رسانه ارائه میدهد. میتوانید چیدمان آن را با ارائه ترکیبپذیرهای خود به
جایگاههای محتوای آن سفارشیسازی کنید، یا میتوانید از شیء PlayerDefaults برای
سفارشیسازی بخشهای خاصی از واسط کاربر، مانند TopControls، CenterControls،
BottomControls، و ErrorOverlay استفاده کنید.
شکل ۱ نشان میدهد که ترکیبپذیر Player جایگاههایش را کجا قرار میدهد. شکل ۲ ترتیب ترسیم لایهها را نشان میدهد.
Player.
کنترلهای وسط و پایین، جایگاههای
PlayerDefaults.CenterControls و
PlayerDefaults.BottomControls را نشان میدهند. topControls،
above، و below بهطور پیشفرض خالی هستند.Player ترکیبپذیر،
شمارهگذاریشده بهترتیب ترسیم.برای مثال، میتوانید از PlayerDefaults.CenterControls برای لغو کردن فقط
دکمه مرکزی پخش/ مکث استفاده کنید و بقیه کنترلهای مرکزی را دستنخورده باقی بگذارید.
همچنین میتوانید یک ترکیبپذیر کاملاً سفارشی را برای پارامتری مانند 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 ) }
Player composable همچنین زیرنویسها را نمایش میدهد و وقتی ویدیویی برای نمایش وجود ندارد، اثر هنری فایل رسانهای فعلی را نشان میدهد. برای سفارشیسازی ظاهر آنها،
ترکیبپذیرهای خودتان را به جایگاههای subtitles و artwork ارسال کنید. برای پنهان کردن آنها،
یک composable خالی را به subtitles و null را به artwork ارسال کنید.
نگهدارندههای حالت واسط کاربر
اگر هیچکدام از مؤلفههای داربست نیازهای شما را برآورده نمیکنند، میتوانید مستقیماً از
اشیاء وضعیت استفاده کنید. بهطورکلی توصیه میشود از روشهای
remember مربوطه برای حفظ ظاهر واسط کاربر بین بازترکیبها استفاده کنید.
برای درک بهتر نحوه استفاده از انعطافپذیری نگهدارندههای حالت میانای کاربر درمقابل ترکیبپذیرها، درباره نحوه مدیریت حالت توسط Compose بخوانید.
نگهدارندههای حالت دکمه
برای برخیاز حالتهای میانای کاربر، کتابخانه فرض میکند که به احتمال زیاد توسط ترکیبپذیرهای دکمهمانند مصرف خواهند شد.
| ایالت | remember*State | نوع |
|---|---|---|
PlayPauseButtonState |
rememberPlayPauseButtonState |
تغییر وضعیت ۲ یا تغییر وضعیت N |
PreviousButtonState |
rememberPreviousButtonState |
ثابت |
NextButtonState |
rememberNextButtonState |
ثابت |
RepeatButtonState |
rememberRepeatButtonState |
تغییر وضعیت ۳ |
ShuffleButtonState |
rememberShuffleButtonState |
تغییر وضعیت ۲ |
PlaybackSpeedState |
rememberPlaybackSpeedState |
منو یا کلید N |
علاوهبر وضعیتهای پخش و مکث، PlayPauseButtonState
showBuffering، showReplay، و showRetry را نشان میدهد. از آنها برای نشان دادن نشانگر میانگیری درحین میانگیری پخشکننده، نماد بازپخش پساز پایان بازپخش، و نماد امتحان مجدد پساز خطای بازپخش استفاده کنید. showReplay و showRetry پالایش
showPlay: هرگاه یکی از آنها true باشد، showPlay نیز true است، بنابراین آنها را قبلاز showPlay بررسی کنید. روش onClick این حالتها را نیز مدیریت میکند، برای مثال با آمادهسازی مجدد پخشکننده پساز بروز خطا.
برای انتخاب اینکه کدامیک از این وضعیتها نشان داده شود، ترکیبی از
SHOW_BUFFERING_WHEN_PLAYING، SHOW_BUFFERING_ALWAYS، SHOW_REPLAY_ON_ENDED،
و SHOW_RETRY_ON_ERROR را به پارامتر displayMode از
rememberPlayPauseButtonState ارسال کنید. بهطور پیشفرض، displayMode
SHOW_BUFFERING_ALWAYS، SHOW_REPLAY_ON_ENDED، و SHOW_RETRY_ON_ERROR را ترکیب میکند،
که هر سه حالت را نشان میدهد، و حالت میانگیری حتی زمانی که
بازپخش موقتاً متوقف شده است ظاهر میشود.
نمونه استفاده از 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 اطلاعاتی درباره وضعیت خطای فعلی پخشکننده ارائه میدهد،
که میتوان از آن برای نمایش همپوشانی خطا استفاده کرد.
PlaybackState وضعیت بازپخش اصلی پخشکننده را، مانند
playbackState، isPlaying، و playerError، مستقل از هر مؤلفه واسط کاربر خاصی
نمایش میدهد. از آن برای هدایت همپوشانیها یا چیدمانهای سفارشی استفاده کنید، برای مثال برای نشان دادن
نشانگر بارگیری درحالیکه پخشکننده درحال میانگیری است. برای نشانگر Material3 ازپیشساخته، از BufferingIndicator استفاده کنید.
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 برای مقیاسبندی Surface
به نسبت ابعادی انتخابشده (برای انواع بیشتر، به مقیاس محتوا مراجعه کنید) و
presentationState.coverSurface برای دانستن اینکه چه زمانی برای نمایش Surface مناسب نیست استفاده کنیم. در این حالت، میتوانید یک شاتر مات را در بالای سطح قرار دهید که وقتی سطح آماده شد، ناپدید میشود.
ContentFrame به شما امکان میدهد شاتر را بهعنوان یک لامبدا دنبالهدار سفارشیسازی کنید، اما بهطور پیشفرض یک @Composable Box سیاه خواهد بود که اندازه محفظه والد را پر میکند.
PresentationState همچنین showArtwork را نشان میدهد که وقتی فایل رسانهای فعلی بدون قطعه ویدیویی پخش میشود، مثلاً محتوای فقط صوتی، true است. در
این حالت، ContentFrame ترکیبپذیر Artwork را نمایش میدهد که
اثر هنری را از MediaMetadata مورد نشان میدهد. برای سفارشیسازی یا پنهان کردن کار هنری،
ترکیبپذیر خودتان یا null را به جایگاه artwork از ContentFrame ارسال کنید.
بهطور پیشفرض، Artwork فقط دادههای اثر هنری جاسازیشده در فراداده یا از
URI content، file، یا android.resource را بار میکند. برای بار کردن کار هنری از URIهای راه دور
یا منابع دیگر، از یکی از گزینههای زیر استفاده کنید:
-
BitmapLoader، مثلاًDataSourceBitmapLoader، را بهArtworkارسال کنید. - از سربار
Artworkکهpainterرا میگیرد برای بار کردن تصاویر با کتابخانه انتخابیتان استفاده کنید.
برخلاف Player composable که بهطور پیشفرض زیرنویسها را ارائه میکند،
ContentFrame جایگاه subtitles خود را خالی میگذارد. برای رندر کردن زیرنویسها در
ContentFrame، یا برای سفارشیسازی رندر کردن زیرنویس در Player، نشانههای فعلی پخشکننده را با CuesState مشاهده کنید و آنها را در جایگاه subtitles رندر کنید. ContentFrame این جایگاه را در محدوده ویدیو مقیاسبندیشده قرار میدهد
و آن را بالای شاتر و اثر هنری میکشد تا زیرنویسها
هنگامی که ویدیویی وجود ندارد، قابلمشاهده باقی بمانند.
نگهدارندههای حالت انتخاب قطعه
TrackSelectionState به شما کمک میکند منویی برای یک نوع قطعه بسازید، مثلاً
منوی زبان صوتی یا زیرنویس. آن را با
rememberTrackSelectionState ایجاد کنید، نوع قطعه و ترکیبی از
فیلترها، مانند TRACK_FILTER_LANGUAGE یا TRACK_FILTER_RESOLUTION را ارسال کنید که
تعیین میکنند کدام ویژگیهای قطعه گزینهها را متمایز میکنند. hasOffOption یا
hasAutoOption را روی true تنظیم کنید تا گزینهای اضافه شود که نوع قطعه را غیرفعال میکند یا گزینهای
که به پخشکننده اجازه میدهد قطعهها را بهطور خودکار انتخاب کند.
این وضعیت selectionOptions دردسترس و selectedOption را نشان میدهد.
هر TrackSelectionOption نشان میدهد که آیا دستگاه از آن پشتیبانی میکند و آیا
انتخاب شده است یا نه. ارسال گزینهای به selectOption، پارامترهای انتخاب قطعه پخشکننده را بهطور متناسب بهروز میکند. قبلاز نمایش منو، canSetTrackSelectionParameters و اینکه آیا گزینههای کافی برای انتخاب وجود دارد یا نه را بررسی کنید. برای مثال، منو زیرنویس زیر یک گزینه برای هر زبان زیرنویس و یک گزینه برای خاموش کردن زیرنویس ارائه میدهد:
@Composable fun SubtitleMenu(player: Player, modifier: Modifier = Modifier) { val state = rememberTrackSelectionState( player, trackType = C.TRACK_TYPE_TEXT, trackFilters = TRACK_FILTER_LANGUAGE, hasOffOption = true, ) // Hide the menu if the player can't change tracks or has no subtitles to choose from if (!state.canSetTrackSelectionParameters || state.selectionOptions.size < 2) return Column(modifier.selectableGroup()) { for (option in state.selectionOptions) { Row( Modifier.fillMaxWidth() .heightIn(min = 48.dp) .selectable( selected = option.isSelected, enabled = option.isSupported, role = Role.RadioButton, onClick = { state.selectOption(option) }, ) .padding(horizontal = 16.dp), horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically, ) { RadioButton(selected = option.isSelected, onClick = null, enabled = option.isSupported) Text( text = if (option.onOffState == TRACK_SELECTION_OFF) "Off" else option.language?.let { Locale.forLanguageTag(it).displayName } ?: "Unknown", color = if (option.isSupported) Color.Unspecified else LocalContentColor.current.copy(alpha = 0.38f), ) } } } }
اگر به دسترسی مستقیم به Tracks و TrackSelectionParameters بازیکن نیاز دارید،
از TrackSelectionParametersState استفاده کنید. برای
اطلاعات بیشتر درباره نحوه انتخاب قطعه توسط پخشکننده، به انتخاب قطعه مراجعه کنید.
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تبدیل خواهد شد. - میتواند در چندین مکان در سراسر درخت UI ایجاد شود و همیشه نمای ثابتی از وضعیت 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 نباشد.