একটি কম্পোজ ভিডিও প্লেয়ার দিয়ে আপনার অ্যাপে পিকচার-ইন-পিকচার (পিআইপি) যোগ করুন

পিকচার-ইন-পিকচার (পিআইপি) হল একটি বিশেষ ধরনের মাল্টি-উইন্ডো মোড যা বেশিরভাগ ভিডিও প্লেব্যাকের জন্য ব্যবহৃত হয়। এটি ব্যবহারকারীকে অ্যাপের মধ্যে নেভিগেট করার সময় বা প্রধান স্ক্রিনে সামগ্রী ব্রাউজ করার সময় স্ক্রিনের একটি কোণায় পিন করা একটি ছোট উইন্ডোতে একটি ভিডিও দেখতে দেয়।

PiP পিন করা ভিডিও ওভারলে উইন্ডো প্রদান করার জন্য Android 7.0-এ উপলব্ধ মাল্টি-উইন্ডো API-গুলি ব্যবহার করে। আপনার অ্যাপে PiP যোগ করতে, আপনাকে আপনার অ্যাক্টিভিটি রেজিস্টার করতে হবে, আপনার অ্যাক্টিভিটিকে প্রয়োজন অনুযায়ী PiP মোডে স্যুইচ করতে হবে এবং নিশ্চিত করুন যে UI উপাদান লুকানো আছে এবং যখন অ্যাক্টিভিটি PiP মোডে থাকে তখন ভিডিও প্লেব্যাক চলতে থাকে।

এই নির্দেশিকাটি বর্ণনা করে যে কীভাবে একটি কম্পোজ ভিডিও বাস্তবায়নের মাধ্যমে আপনার অ্যাপে কম্পোজে PiP যোগ করবেন। এই সর্বোত্তম অনুশীলনগুলি কার্যকরভাবে দেখতে Socialite অ্যাপটি দেখুন৷

PiP এর জন্য আপনার অ্যাপ সেট আপ করুন

আপনার AndroidManifest.xml ফাইলের কার্যকলাপ ট্যাগে, নিম্নলিখিতগুলি করুন:

  1. আপনি আপনার অ্যাপে PiP ব্যবহার করবেন তা ঘোষণা করতে supportsPictureInPicture যোগ করুন এবং এটিকে true সেট করুন।
  2. 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">

আপনার রচনা কোডে, নিম্নলিখিতগুলি করুন:

  1. 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 যোগ করতে এই পদক্ষেপগুলি অনুসরণ করুন:

  1. একটি সংস্করণ গেট যোগ করুন যাতে এই কোডটি শুধুমাত্র R পর্যন্ত O সংস্করণে অ্যাক্সেস করা যায়।
  2. কী হিসাবে Context সহ একটি DisposableEffect ব্যবহার করুন।
  3. DisposableEffect এর ভিতরে, যখন একটি ল্যাম্বডা ব্যবহার করে onUserLeaveHintProvider ট্রিগার হয় তখন আচরণটি সংজ্ঞায়িত করুন। ল্যাম্বডাতে, findActivity() ) এ enterPictureInPictureMode() কল করুন এবং PictureInPictureParams.Builder().build() এ পাস করুন।
  4. findActivity() ব্যবহার করে addOnUserLeaveHintListener যোগ করুন এবং ল্যাম্বডায় পাস করুন।
  5. 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 একটি মডিফায়ারের মাধ্যমে যোগ করা হয় যা অ্যাপের ভিডিও প্লেয়ারে পাস করা হয়।

  1. একটি modifier তৈরি করুন এবং এটিতে onGloballyPositioned কল করুন। লেআউট স্থানাঙ্কগুলি পরবর্তী ধাপে ব্যবহার করা হবে।
  2. PictureInPictureParams.Builder() এর জন্য একটি ভেরিয়েবল তৈরি করুন।
  3. SDK S বা তার উপরে কিনা তা পরীক্ষা করতে একটি if স্টেটমেন্ট যোগ করুন। যদি তাই হয়, বিল্ডারে setAutoEnterEnabled যোগ করুন এবং সোয়াইপ করার সময় PiP মোডে প্রবেশ করতে এটিকে true সেট করুন। এটি enterPictureInPictureMode মধ্য দিয়ে যাওয়ার চেয়ে একটি মসৃণ অ্যানিমেশন প্রদান করে।
  4. 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)

  1. যেহেতু PiP প্রি-12 যোগ করার জন্য একটি DisposableEffect ব্যবহার করা হয়, তাই আপনাকে আপনার স্টেট ভেরিয়েবল হিসাবে newValue সেট করে rememberUpdatedState দ্বারা একটি নতুন ভেরিয়েবল তৈরি করতে হবে। এটি নিশ্চিত করবে যে আপডেট করা সংস্করণটি DisposableEffect মধ্যে ব্যবহার করা হয়েছে।
  2. যখন 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 যোগ করুন।

  1. শুধুমাত্র builder setSourceRectHint() যোগ করুন যদি রাষ্ট্র সংজ্ঞায়িত করে যে অ্যাপটি PiP মোডে প্রবেশ করবে। যখন অ্যাপটিকে PiP-এ প্রবেশ করার প্রয়োজন হয় না তখন এটি sourceRect গণনা করা এড়িয়ে যায়।
  2. sourceRect মান সেট করতে, মডিফায়ারে onGloballyPositioned ফাংশন থেকে দেওয়া layoutCoordinates ব্যবহার করুন।
  3. 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 তৈরি করুন।

  1. আপনার সম্প্রচার নিয়ন্ত্রণের জন্য ধ্রুবক যোগ করুন:
    // 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
  2. আপনার PiP উইন্ডোতে নিয়ন্ত্রণের জন্য RemoteActions এর একটি তালিকা তৈরি করুন।
  3. এরপরে, একটি 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)
            }
        }
    }
  4. আপনার দূরবর্তী ক্রিয়াগুলির একটি তালিকা 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 যোগ করার সর্বোত্তম অনুশীলনগুলি শিখেছেন।