পিকচার-ইন-পিকচার (পিআইপি) হল একটি বিশেষ ধরনের মাল্টি-উইন্ডো মোড যা বেশিরভাগ ভিডিও প্লেব্যাকের জন্য ব্যবহৃত হয়। এটি ব্যবহারকারীকে অ্যাপের মধ্যে নেভিগেট করার সময় বা প্রধান স্ক্রিনে সামগ্রী ব্রাউজ করার সময় স্ক্রিনের একটি কোণায় পিন করা একটি ছোট উইন্ডোতে একটি ভিডিও দেখতে দেয়।
PiP পিন করা ভিডিও ওভারলে উইন্ডো প্রদান করার জন্য Android 7.0-এ উপলব্ধ মাল্টি-উইন্ডো API-গুলি ব্যবহার করে। আপনার অ্যাপে PiP যোগ করতে, আপনাকে আপনার অ্যাক্টিভিটি রেজিস্টার করতে হবে, আপনার অ্যাক্টিভিটিকে প্রয়োজন অনুযায়ী PiP মোডে স্যুইচ করতে হবে এবং নিশ্চিত করুন যে UI উপাদান লুকানো আছে এবং যখন অ্যাক্টিভিটি PiP মোডে থাকে তখন ভিডিও প্লেব্যাক চলতে থাকে।
এই নির্দেশিকাটি বর্ণনা করে যে কীভাবে একটি কম্পোজ ভিডিও বাস্তবায়নের মাধ্যমে আপনার অ্যাপে কম্পোজে PiP যোগ করবেন। এই সর্বোত্তম অনুশীলনগুলি কার্যকরভাবে দেখতে Socialite অ্যাপটি দেখুন৷
PiP এর জন্য আপনার অ্যাপ সেট আপ করুন
আপনার AndroidManifest.xml
ফাইলের কার্যকলাপ ট্যাগে, নিম্নলিখিতগুলি করুন:
- আপনি আপনার অ্যাপে PiP ব্যবহার করবেন তা ঘোষণা করতে
supportsPictureInPicture
যোগ করুন এবং এটিকেtrue
সেট করুন। configChanges
যোগ করুন এবং এটিকেorientation|screenLayout|screenSize|smallestScreenSize
এইভাবে, PiP মোড ট্রানজিশনের সময় লেআউট পরিবর্তন ঘটলে আপনার কার্যকলাপ পুনরায় চালু হয় না।<activity android:name=".SnippetsActivity" android:exported="true" android:supportsPictureInPicture="true" android:configChanges="orientation|screenLayout|screenSize|smallestScreenSize" android:theme="@style/Theme.Snippets">
আপনার রচনা কোডে, নিম্নলিখিতগুলি করুন:
-
Context
এই এক্সটেনশন যোগ করুন। ক্রিয়াকলাপটি অ্যাক্সেস করতে আপনি এই এক্সটেনশনটি একাধিকবার গাইড জুড়ে ব্যবহার করবেন।internal fun Context.findActivity(): ComponentActivity { var context = this while (context is ContextWrapper) { if (context is ComponentActivity) return context context = context.baseContext } throw IllegalStateException("Picture in picture should be called in the context of an Activity") }
প্রি-অ্যান্ড্রয়েড 12-এর জন্য ছুটিতে PiP যোগ করুন
প্রি-Android 12-এর জন্য PiP যোগ করতে, addOnUserLeaveHintProvider
ব্যবহার করুন। প্রি-অ্যান্ড্রয়েড 12-এর জন্য PiP যোগ করতে এই পদক্ষেপগুলি অনুসরণ করুন:
- একটি সংস্করণ গেট যোগ করুন যাতে এই কোডটি শুধুমাত্র R পর্যন্ত O সংস্করণে অ্যাক্সেস করা যায়।
- কী হিসাবে
Context
সহ একটিDisposableEffect
ব্যবহার করুন। -
DisposableEffect
এর ভিতরে, যখন একটি ল্যাম্বডা ব্যবহার করেonUserLeaveHintProvider
ট্রিগার হয় তখন আচরণটি সংজ্ঞায়িত করুন। ল্যাম্বডাতে,findActivity()
) এenterPictureInPictureMode()
কল করুন এবংPictureInPictureParams.Builder().build()
এ পাস করুন। -
findActivity()
ব্যবহার করেaddOnUserLeaveHintListener
যোগ করুন এবং ল্যাম্বডায় পাস করুন। -
onDispose
এ,findActivity()
ব্যবহার করেremoveOnUserLeaveHintListener
যোগ করুন এবং ল্যাম্বডায় পাস করুন।
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && Build.VERSION.SDK_INT < Build.VERSION_CODES.S ) { val context = LocalContext.current DisposableEffect(context) { val onUserLeaveBehavior: () -> Unit = { context.findActivity() .enterPictureInPictureMode(PictureInPictureParams.Builder().build()) } context.findActivity().addOnUserLeaveHintListener( onUserLeaveBehavior ) onDispose { context.findActivity().removeOnUserLeaveHintListener( onUserLeaveBehavior ) } } } else { Log.i("PiP info", "API does not support PiP") }
পোস্ট-অ্যান্ড্রয়েড 12-এর জন্য ছুটিতে PiP যোগ করুন
অ্যান্ড্রয়েড 12-এর পরে, PictureInPictureParams.Builder
একটি মডিফায়ারের মাধ্যমে যোগ করা হয় যা অ্যাপের ভিডিও প্লেয়ারে পাস করা হয়।
- একটি
modifier
তৈরি করুন এবং এটিতেonGloballyPositioned
কল করুন। লেআউট স্থানাঙ্কগুলি পরবর্তী ধাপে ব্যবহার করা হবে। -
PictureInPictureParams.Builder()
এর জন্য একটি ভেরিয়েবল তৈরি করুন। - SDK S বা তার উপরে কিনা তা পরীক্ষা করতে একটি
if
স্টেটমেন্ট যোগ করুন। যদি তাই হয়, বিল্ডারেsetAutoEnterEnabled
যোগ করুন এবং সোয়াইপ করার সময় PiP মোডে প্রবেশ করতে এটিকেtrue
সেট করুন। এটিenterPictureInPictureMode
মধ্য দিয়ে যাওয়ার চেয়ে একটি মসৃণ অ্যানিমেশন প্রদান করে। -
setPictureInPictureParams()
কল করতেfindActivity()
ব্যবহার করুন।builder
build()
কল করুন এবং এটি পাস করুন।
val pipModifier = modifier.onGloballyPositioned { layoutCoordinates -> val builder = PictureInPictureParams.Builder() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { builder.setAutoEnterEnabled(true) } context.findActivity().setPictureInPictureParams(builder.build()) } VideoPlayer(pipModifier)
একটি বোতামের মাধ্যমে PiP যোগ করুন
একটি বোতাম ক্লিকের মাধ্যমে PiP মোডে প্রবেশ করতে, findActivity()
এ enterPictureInPictureMode()
কল করুন।
PictureInPictureParams.Builder
এ পূর্ববর্তী কল দ্বারা পরামিতিগুলি ইতিমধ্যেই সেট করা হয়েছে, তাই আপনাকে বিল্ডারে নতুন প্যারামিটার সেট করতে হবে না। যাইহোক, যদি আপনি বোতামে ক্লিকে কোনো পরামিতি পরিবর্তন করতে চান, আপনি সেগুলি এখানে সেট করতে পারেন।
val context = LocalContext.current Button(onClick = { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { context.findActivity().enterPictureInPictureMode( PictureInPictureParams.Builder().build() ) } else { Log.i(PIP_TAG, "API does not support PiP") } }) { Text(text = "Enter PiP mode!") }
PiP মোডে আপনার UI পরিচালনা করুন
আপনি যখন PiP মোডে প্রবেশ করেন, তখন আপনার অ্যাপের সম্পূর্ণ UI PiP উইন্ডোতে প্রবেশ করে, যদি না আপনি উল্লেখ করেন যে আপনার UI কে PiP মোডে এবং এর বাইরে দেখতে হবে।
প্রথমে আপনাকে জানতে হবে আপনার অ্যাপ কখন PiP মোডে আছে কি না। আপনি এটি অর্জন করতে OnPictureInPictureModeChangedProvider
ব্যবহার করতে পারেন। আপনার অ্যাপ PiP মোডে আছে কিনা তা নিচের কোডটি আপনাকে বলে।
@Composable fun rememberIsInPipMode(): Boolean { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val activity = LocalContext.current.findActivity() var pipMode by remember { mutableStateOf(activity.isInPictureInPictureMode) } DisposableEffect(activity) { val observer = Consumer<PictureInPictureModeChangedInfo> { info -> pipMode = info.isInPictureInPictureMode } activity.addOnPictureInPictureModeChangedListener( observer ) onDispose { activity.removeOnPictureInPictureModeChangedListener(observer) } } return pipMode } else { return false } }
এখন, অ্যাপটি যখন PiP মোডে প্রবেশ করবে তখন কোন UI উপাদানগুলি দেখাতে হবে তা টগল করতে আপনি rememberIsInPipMode()
ব্যবহার করতে পারেন:
val inPipMode = rememberIsInPipMode() Column(modifier = modifier) { // This text will only show up when the app is not in PiP mode if (!inPipMode) { Text( text = "Picture in Picture", ) } VideoPlayer() }
নিশ্চিত করুন যে আপনার অ্যাপটি সঠিক সময়ে PiP মোডে প্রবেশ করেছে
নিম্নলিখিত পরিস্থিতিতে আপনার অ্যাপের PiP মোডে প্রবেশ করা উচিত নয়:
- ভিডিও বন্ধ বা পজ করা হলে।
- আপনি যদি ভিডিও প্লেয়ারের চেয়ে অ্যাপের একটি ভিন্ন পৃষ্ঠায় থাকেন।
আপনার অ্যাপ কখন PiP মোডে প্রবেশ করে তা নিয়ন্ত্রণ করতে, একটি পরিবর্তনশীল যোগ করুন যা একটি mutableStateOf
ব্যবহার করে ভিডিও প্লেয়ারের অবস্থা ট্র্যাক করে।
ভিডিও চলছে কিনা তার উপর ভিত্তি করে স্থিতি টগল করুন
ভিডিও প্লেয়ার বাজছে কিনা তার উপর ভিত্তি করে স্থিতি টগল করতে, ভিডিও প্লেয়ারে একজন শ্রোতা যোগ করুন। প্লেয়ার খেলছে কি না তার উপর ভিত্তি করে আপনার স্টেট ভেরিয়েবলের অবস্থা টগল করুন:
player.addListener(object : Player.Listener { override fun onIsPlayingChanged(isPlaying: Boolean) { shouldEnterPipMode = isPlaying } })
প্লেয়ার রিলিজ হয়েছে কিনা তার উপর ভিত্তি করে টগল স্টেট
প্লেয়ার রিলিজ হলে, আপনার স্টেট ভেরিয়েবলকে false
সেট করুন:
fun releasePlayer() { shouldEnterPipMode = false }
PiP মোড প্রবেশ করা হয়েছে কিনা তা নির্ধারণ করতে রাষ্ট্র ব্যবহার করুন (প্রি-অ্যান্ড্রয়েড 12)
- যেহেতু PiP প্রি-12 যোগ করার জন্য একটি
DisposableEffect
ব্যবহার করা হয়, তাই আপনাকে আপনার স্টেট ভেরিয়েবল হিসাবেnewValue
সেট করেrememberUpdatedState
দ্বারা একটি নতুন ভেরিয়েবল তৈরি করতে হবে। এটি নিশ্চিত করবে যে আপডেট করা সংস্করণটিDisposableEffect
মধ্যে ব্যবহার করা হয়েছে। যখন
OnUserLeaveHintListener
ট্রিগার করা হয় তখন ল্যাম্বডা-তে আচরণ সংজ্ঞায়িত করে,enterPictureInPictureMode()
এ কলের চারপাশে স্টেট ভেরিয়েবল সহ একটিif
স্টেটমেন্ট যোগ করুন :val currentShouldEnterPipMode by rememberUpdatedState(newValue = shouldEnterPipMode) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && Build.VERSION.SDK_INT < Build.VERSION_CODES.S ) { val context = LocalContext.current DisposableEffect(context) { val onUserLeaveBehavior: () -> Unit = { if (currentShouldEnterPipMode) { context.findActivity() .enterPictureInPictureMode(PictureInPictureParams.Builder().build()) } } context.findActivity().addOnUserLeaveHintListener( onUserLeaveBehavior ) onDispose { context.findActivity().removeOnUserLeaveHintListener( onUserLeaveBehavior ) } } } else { Log.i("PiP info", "API does not support PiP") }
PiP মোড প্রবেশ করা হয়েছে কিনা তা নির্ধারণ করতে রাষ্ট্র ব্যবহার করুন (অ্যান্ড্রয়েড 12-পরবর্তী)
আপনার স্টেট ভেরিয়েবলকে setAutoEnterEnabled
এ পাস করুন যাতে আপনার অ্যাপ সঠিক সময়ে শুধুমাত্র PiP মোডে প্রবেশ করে:
val pipModifier = modifier.onGloballyPositioned { layoutCoordinates -> val builder = PictureInPictureParams.Builder() // Add autoEnterEnabled for versions S and up if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { builder.setAutoEnterEnabled(shouldEnterPipMode) } context.findActivity().setPictureInPictureParams(builder.build()) } VideoPlayer(pipModifier)
একটি মসৃণ অ্যানিমেশন বাস্তবায়ন করতে setSourceRectHint
ব্যবহার করুন
setSourceRectHint
API PiP মোডে প্রবেশের জন্য একটি মসৃণ অ্যানিমেশন তৈরি করে। Android 12+ এ, এটি PiP মোড থেকে বেরিয়ে আসার জন্য একটি মসৃণ অ্যানিমেশন তৈরি করে। PiP-এ রূপান্তরের পরে দৃশ্যমান কার্যকলাপের ক্ষেত্রটি নির্দেশ করতে PiP বিল্ডারে এই API যোগ করুন।
- শুধুমাত্র
builder
setSourceRectHint()
যোগ করুন যদি রাষ্ট্র সংজ্ঞায়িত করে যে অ্যাপটি PiP মোডে প্রবেশ করবে। যখন অ্যাপটিকে PiP-এ প্রবেশ করার প্রয়োজন হয় না তখন এটিsourceRect
গণনা করা এড়িয়ে যায়। -
sourceRect
মান সেট করতে, মডিফায়ারেonGloballyPositioned
ফাংশন থেকে দেওয়াlayoutCoordinates
ব্যবহার করুন। -
builder
setSourceRectHint()
কল করুন এবংsourceRect
ভেরিয়েবলে পাস করুন।
val context = LocalContext.current val pipModifier = modifier.onGloballyPositioned { layoutCoordinates -> val builder = PictureInPictureParams.Builder() if (shouldEnterPipMode) { val sourceRect = layoutCoordinates.boundsInWindow().toAndroidRectF().toRect() builder.setSourceRectHint(sourceRect) } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { builder.setAutoEnterEnabled(shouldEnterPipMode) } context.findActivity().setPictureInPictureParams(builder.build()) } VideoPlayer(pipModifier)
PiP উইন্ডোর আকৃতির অনুপাত সেট করতে setAspectRatio
ব্যবহার করুন
PiP উইন্ডোর আকৃতির অনুপাত সেট করতে, আপনি হয় একটি নির্দিষ্ট অনুপাত চয়ন করতে পারেন বা প্লেয়ারের ভিডিও আকারের প্রস্থ এবং উচ্চতা ব্যবহার করতে পারেন৷ আপনি যদি মিডিয়া3 প্লেয়ার ব্যবহার করেন, তাহলে চেক করুন যে প্লেয়ারটি শূন্য নয় এবং প্লেয়ারের ভিডিও সাইজ VideoSize.UNKNOWN
এর সমান নয়। আকৃতির অনুপাত সেট করার আগে UNKNOWN।
val context = LocalContext.current val pipModifier = modifier.onGloballyPositioned { layoutCoordinates -> val builder = PictureInPictureParams.Builder() if (shouldEnterPipMode && player != null && player.videoSize != VideoSize.UNKNOWN) { val sourceRect = layoutCoordinates.boundsInWindow().toAndroidRectF().toRect() builder.setSourceRectHint(sourceRect) builder.setAspectRatio( Rational(player.videoSize.width, player.videoSize.height) ) } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { builder.setAutoEnterEnabled(shouldEnterPipMode) } context.findActivity().setPictureInPictureParams(builder.build()) } VideoPlayer(pipModifier)
আপনি যদি একটি কাস্টম প্লেয়ার ব্যবহার করেন তবে আপনার প্লেয়ারের জন্য নির্দিষ্ট সিনট্যাক্স ব্যবহার করে প্লেয়ারের উচ্চতা এবং প্রস্থের দিক অনুপাত সেট করুন। সচেতন থাকুন যে যদি আপনার প্লেয়ারটি শুরু করার সময় আকার পরিবর্তন করে, যদি এটি আকৃতির অনুপাতের বৈধ সীমার বাইরে পড়ে তবে আপনার অ্যাপটি ক্র্যাশ হয়ে যাবে। মিডিয়া3 প্লেয়ারের ক্ষেত্রে যেভাবে করা হয় তার অনুরূপ আকৃতির অনুপাত গণনা করার সময় আপনাকে চেক যোগ করতে হতে পারে।
দূরবর্তী কর্ম যোগ করুন
আপনি যদি আপনার PiP উইন্ডোতে নিয়ন্ত্রণ (খেলা, বিরতি, ইত্যাদি) যোগ করতে চান, আপনি যোগ করতে চান এমন প্রতিটি নিয়ন্ত্রণের জন্য একটি RemoteAction
তৈরি করুন।
- আপনার সম্প্রচার নিয়ন্ত্রণের জন্য ধ্রুবক যোগ করুন:
// Constant for broadcast receiver const val ACTION_BROADCAST_CONTROL = "broadcast_control" // Intent extras for broadcast controls from Picture-in-Picture mode. const val EXTRA_CONTROL_TYPE = "control_type" const val EXTRA_CONTROL_PLAY = 1 const val EXTRA_CONTROL_PAUSE = 2
- আপনার PiP উইন্ডোতে নিয়ন্ত্রণের জন্য
RemoteActions
এর একটি তালিকা তৈরি করুন। - এরপরে, একটি
BroadcastReceiver
যোগ করুন এবং প্রতিটি বোতামের ক্রিয়া সেট করতেonReceive()
ওভাররাইড করুন। রিসিভার এবং রিমোট অ্যাকশন নিবন্ধন করতে একটিDisposableEffect
ব্যবহার করুন। প্লেয়ারটি নিষ্পত্তি হয়ে গেলে, রিসিভারটিকে নিবন্ধনমুক্ত করুন।@RequiresApi(Build.VERSION_CODES.O) @Composable fun PlayerBroadcastReceiver(player: Player?) { val isInPipMode = rememberIsInPipMode() if (!isInPipMode || player == null) { // Broadcast receiver is only used if app is in PiP mode and player is non null return } val context = LocalContext.current DisposableEffect(player) { val broadcastReceiver: BroadcastReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { if ((intent == null) || (intent.action != ACTION_BROADCAST_CONTROL)) { return } when (intent.getIntExtra(EXTRA_CONTROL_TYPE, 0)) { EXTRA_CONTROL_PAUSE -> player.pause() EXTRA_CONTROL_PLAY -> player.play() } } } ContextCompat.registerReceiver( context, broadcastReceiver, IntentFilter(ACTION_BROADCAST_CONTROL), ContextCompat.RECEIVER_NOT_EXPORTED ) onDispose { context.unregisterReceiver(broadcastReceiver) } } }
- আপনার দূরবর্তী ক্রিয়াগুলির একটি তালিকা
PictureInPictureParams.Builder
এ পাঠান:val context = LocalContext.current val pipModifier = modifier.onGloballyPositioned { layoutCoordinates -> val builder = PictureInPictureParams.Builder() builder.setActions( listOfRemoteActions() ) if (shouldEnterPipMode && player != null && player.videoSize != VideoSize.UNKNOWN) { val sourceRect = layoutCoordinates.boundsInWindow().toAndroidRectF().toRect() builder.setSourceRectHint(sourceRect) builder.setAspectRatio( Rational(player.videoSize.width, player.videoSize.height) ) } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { builder.setAutoEnterEnabled(shouldEnterPipMode) } context.findActivity().setPictureInPictureParams(builder.build()) } VideoPlayer(modifier = pipModifier)
পরবর্তী পদক্ষেপ
এই গাইডে আপনি প্রি-অ্যান্ড্রয়েড 12 এবং পোস্ট-অ্যান্ড্রয়েড 12 উভয় কম্পোজে PiP যোগ করার সর্বোত্তম অনুশীলনগুলি শিখেছেন।
- কম্পোজ PiP-এর সর্বোত্তম অনুশীলনগুলি কার্যকরভাবে দেখতে Socialite অ্যাপটি দেখুন৷
- আরও তথ্যের জন্য PiP ডিজাইন নির্দেশিকা দেখুন।