নির্ভরতা যোগ করুন
Media3 লাইব্রেরিতে একটি Jetpack Compose-ভিত্তিক UI মডিউল রয়েছে। এটি ব্যবহার করতে, নিম্নলিখিত নির্ভরতা যোগ করুন:
কোটলিন
implementation("androidx.media3:media3-ui-compose:1.6.0")
গ্রোভি
implementation "androidx.media3:media3-ui-compose:1.6.0"
আমরা আপনাকে আপনার অ্যাপটিকে একটি রচনা-প্রথম ফ্যাশনে বিকাশ করতে বা ভিউ ব্যবহার করা থেকে স্থানান্তর করতে উত্সাহিত করি৷
সম্পূর্ণরূপে কম্পোজ ডেমো অ্যাপ
যদিও media3-ui-compose
লাইব্রেরিতে বাক্সের বাইরের কম্পোজেবল (যেমন বোতাম, সূচক, ছবি বা ডায়ালগ) অন্তর্ভুক্ত নেই, আপনি কম্পোজে সম্পূর্ণরূপে লেখা একটি ডেমো অ্যাপ খুঁজে পেতে পারেন যা AndroidView
এ PlayerView
র্যাপ করার মতো যেকোন আন্তঃঅপারেবিলিটি সমাধান এড়িয়ে যায়। ডেমো অ্যাপটি media3-ui-compose
মডিউল থেকে UI স্টেট হোল্ডার ক্লাস ব্যবহার করে এবং Compose Material3 লাইব্রেরি ব্যবহার করে।
UI স্টেট হোল্ডার
কম্পোজেবল বনাম ইউআই স্টেট হোল্ডারদের নমনীয়তা কীভাবে ব্যবহার করতে পারেন তা আরও ভালভাবে বোঝার জন্য, কম্পোজ কীভাবে স্টেট পরিচালনা করে তা পড়ুন।
বোতাম রাষ্ট্র ধারক
কিছু UI রাজ্যের জন্য, আমরা অনুমান করি যে সেগুলি সম্ভবত বোতামের মতো কম্পোজেবল দ্বারা গ্রাস করা হবে।
রাজ্য | মনে রাখবেন *রাষ্ট্র | টাইপ |
---|---|---|
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
UI অবস্থায় রূপান্তর করাই এর একমাত্র দায়িত্ব।
তারপরে আপনি আপনার পছন্দের লেআউটে বোতামগুলিকে মিশ্রিত করতে এবং মেলাতে পারেন:
Row(
modifier = modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.CenterVertically,
) {
PreviousButton(player)
PlayPauseButton(player)
NextButton(player)
}
ভিজ্যুয়াল আউটপুট স্টেট হোল্ডার
PlayerSurface
ভিডিও আউটপুট কখন দেখানো হতে পারে বা একটি স্থানধারক UI উপাদান দ্বারা কভার করা উচিত তার জন্য PresentationState
তথ্য রাখে।
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
উভয়ই ব্যবহার করতে পারি (আরও ধরণের জন্য ContentScale ডক্স দেখুন) এবং presentationState.coverSurface
কখন সারফেস দেখানোর সময় সঠিক নয় তা জানতে। এই ক্ষেত্রে, আপনি পৃষ্ঠের উপরে একটি অস্বচ্ছ শাটার স্থাপন করতে পারেন, যা পৃষ্ঠটি প্রস্তুত হয়ে গেলে অদৃশ্য হয়ে যাবে।
প্রবাহ কোথায়?
অনেক অ্যান্ড্রয়েড ডেভেলপার সবসময় পরিবর্তনশীল UI ডেটা সংগ্রহ করতে Kotlin Flow
অবজেক্ট ব্যবহার করার সাথে পরিচিত। উদাহরণস্বরূপ, আপনি Player.isPlaying
প্রবাহের সন্ধানে থাকতে পারেন যা আপনি জীবনচক্র-সচেতন পদ্ধতিতে collect
করতে পারেন। অথবা Player.eventsFlow
এর মতো কিছু আপনাকে একটি Flow<Player.Events>
প্রদান করতে যা আপনি আপনার ইচ্ছামত filter
করতে পারেন।
যাইহোক, Player
UI স্টেটের জন্য ফ্লো ব্যবহার করার কিছু ত্রুটি রয়েছে। প্রধান উদ্বেগের মধ্যে একটি হল ডেটা স্থানান্তরের অ্যাসিঙ্ক্রোনাস প্রকৃতি। আমরা Player.Event
মধ্যে যতটা সম্ভব কম লেটেন্সি নিশ্চিত করতে চাই। ইভেন্ট এবং UI সাইডে এর ব্যবহার, Player
সাথে সিঙ্কের বাইরে থাকা UI উপাদানগুলি দেখানো এড়িয়ে চলুন।
অন্যান্য পয়েন্ট অন্তর্ভুক্ত:
- সমস্ত
Player.Events
সাথে একটি প্রবাহ। ইভেন্টগুলি একটি একক দায়িত্ব নীতি মেনে চলে না, প্রতিটি গ্রাহককে প্রাসঙ্গিক ইভেন্টগুলি ফিল্টার করতে হবে৷ - প্রতিটি
Player.Event
জন্য একটি ফ্লো তৈরি করা। ইভেন্টের জন্য আপনাকে প্রতিটি UI উপাদানের জন্য সেগুলিকেcombine
করতে হবে। একটি Player.Event এবং একটি UI উপাদান পরিবর্তনের মধ্যে একটি বহু-থেকে-অনেক ম্যাপিং আছে৷combine
ব্যবহার করার ফলে UI কে সম্ভাব্য অবৈধ অবস্থায় নিয়ে যেতে পারে।
কাস্টম UI স্টেট তৈরি করুন
আপনি কাস্টম UI রাজ্য যোগ করতে পারেন যদি বিদ্যমানগুলি আপনার চাহিদা পূরণ না করে। প্যাটার্নটি অনুলিপি করতে বিদ্যমান রাজ্যের উত্স কোডটি দেখুন। একটি সাধারণ UI স্টেট হোল্ডার ক্লাস নিম্নলিখিতগুলি করে:
- একজন
Player
নেয়। - coroutines ব্যবহার করে
Player
সদস্যতা. আরও বিস্তারিত জানার জন্যPlayer.listen
দেখুন। - এর অভ্যন্তরীণ অবস্থা আপডেট করে নির্দিষ্ট
Player.Events
প্রতি সাড়া দেয়। - ব্যবসা-লজিক কমান্ডগুলি গ্রহণ করুন যা একটি উপযুক্ত
Player
আপডেটে রূপান্তরিত হবে। - UI ট্রি জুড়ে একাধিক জায়গায় তৈরি করা যেতে পারে এবং সর্বদা প্লেয়ারের অবস্থার একটি সামঞ্জস্যপূর্ণ দৃশ্য বজায় রাখবে।
- কম্পোজ
State
ক্ষেত্রগুলিকে প্রকাশ করে যা পরিবর্তনের জন্য গতিশীলভাবে প্রতিক্রিয়া জানাতে কম্পোজেবল দ্বারা গ্রাস করা যেতে পারে। - কম্পোজিশনের মধ্যে উদাহরণ মনে রাখার জন্য একটি
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
যা আপনাকে coroutine জগতে প্রবেশ করতে এবং অনির্দিষ্টকালের জন্য Player.Events
শুনতে দেয়৷ বিভিন্ন UI অবস্থার Media3 বাস্তবায়ন শেষ ডেভেলপারকে Player.Events
সম্পর্কে শেখার বিষয়ে নিজেদের উদ্বিগ্ন না হতে সাহায্য করে।
নির্ভরতা যোগ করুন
Media3 লাইব্রেরিতে একটি Jetpack Compose-ভিত্তিক UI মডিউল রয়েছে। এটি ব্যবহার করতে, নিম্নলিখিত নির্ভরতা যোগ করুন:
কোটলিন
implementation("androidx.media3:media3-ui-compose:1.6.0")
গ্রোভি
implementation "androidx.media3:media3-ui-compose:1.6.0"
আমরা আপনাকে আপনার অ্যাপটিকে একটি রচনা-প্রথম ফ্যাশনে বিকাশ করতে বা ভিউ ব্যবহার করা থেকে স্থানান্তর করতে উত্সাহিত করি৷
সম্পূর্ণরূপে কম্পোজ ডেমো অ্যাপ
যদিও media3-ui-compose
লাইব্রেরিতে বাক্সের বাইরের কম্পোজেবল (যেমন বোতাম, সূচক, ছবি বা ডায়ালগ) অন্তর্ভুক্ত নেই, আপনি কম্পোজে সম্পূর্ণরূপে লেখা একটি ডেমো অ্যাপ খুঁজে পেতে পারেন যা AndroidView
এ PlayerView
র্যাপ করার মতো যেকোন আন্তঃঅপারেবিলিটি সমাধান এড়িয়ে যায়। ডেমো অ্যাপটি media3-ui-compose
মডিউল থেকে UI স্টেট হোল্ডার ক্লাস ব্যবহার করে এবং Compose Material3 লাইব্রেরি ব্যবহার করে।
UI স্টেট হোল্ডার
কম্পোজেবল বনাম ইউআই স্টেট হোল্ডারদের নমনীয়তা কীভাবে ব্যবহার করতে পারেন তা আরও ভালভাবে বোঝার জন্য, কম্পোজ কীভাবে স্টেট পরিচালনা করে তা পড়ুন।
বোতাম রাষ্ট্র ধারক
কিছু UI রাজ্যের জন্য, আমরা অনুমান করি যে সেগুলি সম্ভবত বোতামের মতো কম্পোজেবল দ্বারা গ্রাস করা হবে।
রাজ্য | মনে রাখবেন *রাষ্ট্র | টাইপ |
---|---|---|
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
UI অবস্থায় রূপান্তর করাই এর একমাত্র দায়িত্ব।
তারপরে আপনি আপনার পছন্দের লেআউটে বোতামগুলিকে মিশ্রিত করতে এবং মেলাতে পারেন:
Row(
modifier = modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.CenterVertically,
) {
PreviousButton(player)
PlayPauseButton(player)
NextButton(player)
}
ভিজ্যুয়াল আউটপুট স্টেট হোল্ডার
PlayerSurface
ভিডিও আউটপুট কখন দেখানো হতে পারে বা একটি স্থানধারক UI উপাদান দ্বারা কভার করা উচিত তার জন্য PresentationState
তথ্য রাখে।
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
উভয়ই ব্যবহার করতে পারি (আরও ধরণের জন্য ContentScale ডক্স দেখুন) এবং presentationState.coverSurface
কখন সারফেস দেখানোর সময় সঠিক নয় তা জানতে। এই ক্ষেত্রে, আপনি পৃষ্ঠের উপরে একটি অস্বচ্ছ শাটার স্থাপন করতে পারেন, যা পৃষ্ঠটি প্রস্তুত হয়ে গেলে অদৃশ্য হয়ে যাবে।
প্রবাহ কোথায়?
অনেক অ্যান্ড্রয়েড ডেভেলপার সবসময় পরিবর্তনশীল UI ডেটা সংগ্রহ করতে Kotlin Flow
অবজেক্ট ব্যবহার করার সাথে পরিচিত। উদাহরণস্বরূপ, আপনি Player.isPlaying
প্রবাহের সন্ধানে থাকতে পারেন যা আপনি জীবনচক্র-সচেতন পদ্ধতিতে collect
করতে পারেন। অথবা Player.eventsFlow
এর মতো কিছু আপনাকে একটি Flow<Player.Events>
প্রদান করতে যা আপনি আপনার ইচ্ছামত filter
করতে পারেন।
যাইহোক, Player
UI স্টেটের জন্য ফ্লো ব্যবহার করার কিছু ত্রুটি রয়েছে। প্রধান উদ্বেগের মধ্যে একটি হল ডেটা স্থানান্তরের অ্যাসিঙ্ক্রোনাস প্রকৃতি। আমরা Player.Event
মধ্যে যতটা সম্ভব কম লেটেন্সি নিশ্চিত করতে চাই। ইভেন্ট এবং UI সাইডে এর ব্যবহার, Player
সাথে সিঙ্কের বাইরে থাকা UI উপাদানগুলি দেখানো এড়িয়ে চলুন।
অন্যান্য পয়েন্ট অন্তর্ভুক্ত:
- সমস্ত
Player.Events
সাথে একটি প্রবাহ। ইভেন্টগুলি একটি একক দায়িত্ব নীতি মেনে চলে না, প্রতিটি গ্রাহককে প্রাসঙ্গিক ইভেন্টগুলি ফিল্টার করতে হবে৷ - প্রতিটি
Player.Event
জন্য একটি ফ্লো তৈরি করা। ইভেন্টের জন্য আপনাকে প্রতিটি UI উপাদানের জন্য সেগুলিকেcombine
করতে হবে। একটি Player.Event এবং একটি UI উপাদান পরিবর্তনের মধ্যে একটি বহু-থেকে-অনেক ম্যাপিং আছে৷combine
ব্যবহার করার ফলে UI কে সম্ভাব্য অবৈধ অবস্থায় নিয়ে যেতে পারে।
কাস্টম UI স্টেট তৈরি করুন
আপনি কাস্টম UI রাজ্য যোগ করতে পারেন যদি বিদ্যমানগুলি আপনার চাহিদা পূরণ না করে। প্যাটার্নটি অনুলিপি করতে বিদ্যমান রাজ্যের উত্স কোডটি দেখুন। একটি সাধারণ UI স্টেট হোল্ডার ক্লাস নিম্নলিখিতগুলি করে:
- একজন
Player
নেয়। - coroutines ব্যবহার করে
Player
সদস্যতা. আরও বিস্তারিত জানার জন্যPlayer.listen
দেখুন। - এর অভ্যন্তরীণ অবস্থা আপডেট করে নির্দিষ্ট
Player.Events
প্রতি সাড়া দেয়। - ব্যবসা-লজিক কমান্ডগুলি গ্রহণ করুন যা একটি উপযুক্ত
Player
আপডেটে রূপান্তরিত হবে। - UI ট্রি জুড়ে একাধিক জায়গায় তৈরি করা যেতে পারে এবং সর্বদা প্লেয়ারের অবস্থার একটি সামঞ্জস্যপূর্ণ দৃশ্য বজায় রাখবে।
- কম্পোজ
State
ক্ষেত্রগুলিকে প্রকাশ করে যা পরিবর্তনের জন্য গতিশীলভাবে প্রতিক্রিয়া জানাতে কম্পোজেবল দ্বারা গ্রাস করা যেতে পারে। - কম্পোজিশনের মধ্যে উদাহরণ মনে রাখার জন্য একটি
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
যা আপনাকে coroutine জগতে প্রবেশ করতে এবং অনির্দিষ্টকালের জন্য Player.Events
শুনতে দেয়৷ বিভিন্ন UI অবস্থার Media3 বাস্তবায়ন শেষ ডেভেলপারকে Player.Events
সম্পর্কে শেখার বিষয়ে নিজেদের উদ্বিগ্ন না হতে সাহায্য করে।