'लिखें' वीडियो प्लेयर की मदद से, अपने ऐप्लिकेशन में पिक्चर में पिक्चर (पीआईपी) की सुविधा जोड़ना

पिक्चर में पिक्चर (पीआईपी), एक खास तरह का मल्टी-विंडो मोड है, जिसका इस्तेमाल ज़्यादातर वीडियो चलाना. यह उपयोगकर्ता को छोटी सी विंडो में वीडियो देखने देता है. ऐप्लिकेशन के बीच नेविगेट करते समय या मुख्य स्क्रीन.

पीआईपी, Android 7.0 में उपलब्ध मल्टी-विंडो एपीआई का इस्तेमाल करता है, ताकि पिन की गई वीडियो ओवरले विंडो. अपने ऐप्लिकेशन में पीआईपी (पिक्चर में पिक्चर) जोड़ने के लिए, आपको गतिविधि चुनें, ज़रूरत के हिसाब से अपनी गतिविधि को पीआईपी मोड पर स्विच करें. साथ ही, पक्का करें कि यूज़र इंटरफ़ेस (यूआई) एलिमेंट वे छिपा दिए जाते हैं और गतिविधि के पीआईपी मोड में होने पर, वीडियो चलना जारी रहता है.

इस गाइड में, Compose वीडियो को लागू करके अपने ऐप्लिकेशन में Compose में पिन किए गए वीडियो को जोड़ने का तरीका बताया गया है. इन्हें देखने के लिए, Socialite ऐप्लिकेशन पर जाएं का इस्तेमाल कैसे करना है.

अपने ऐप्लिकेशन को पीआईपी के लिए सेट अप करना

अपनी AndroidManifest.xml फ़ाइल के गतिविधि टैग में, यह तरीका अपनाएं:

  1. supportsPictureInPicture को जोड़ें और true पर सेट करें, ताकि यह एलान किया जा सके कि आपको पीआईपी की सुविधा का इस्तेमाल करके.
  2. configChanges जोड़ें और इसे इस पर सेट करें इसे बताने के लिए orientation|screenLayout|screenSize|smallestScreenSize आपकी गतिविधि, लेआउट कॉन्फ़िगरेशन में किए गए बदलावों को हैंडल करती है. इस तरह, पीआईपी मोड के ट्रांज़िशन के दौरान लेआउट में बदलाव होने पर, आपकी गतिविधि फिर से शुरू नहीं होती.

      <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")
    }

Android 12 से पहले के वर्शन के लिए, छुट्टी वाले ऐप्लिकेशन में पीआईपी (पिक्चर में पिक्चर) जोड़ें

Android 12 से पहले के वर्शन में पीआईपी (पिक्चर में पिक्चर) जोड़ने के लिए, addOnUserLeaveHintProvider का इस्तेमाल करें. फ़ॉलो करें Android 12 से पहले के वर्शन के लिए पीआईपी (पिक्चर में पिक्चर) जोड़ने के लिए यह तरीका अपनाएं:

  1. वर्शन गेट जोड़ें, ताकि इस कोड को सिर्फ़ वर्शन O में R तक ऐक्सेस किया जा सके.
  2. DisposableEffect का इस्तेमाल करें, जिसमें Context कुंजी हो.
  3. DisposableEffect में, लैम्डा का इस्तेमाल करके onUserLeaveHintProvider के ट्रिगर होने पर होने वाले व्यवहार के बारे में बताएं. Labda में कॉल करें findActivity() पर enterPictureInPictureMode() करें और पास करें PictureInPictureParams.Builder().build().
  4. findActivity() का इस्तेमाल करके addOnUserLeaveHintListener जोड़ें और उसे lambda फ़ंक्शन में पास करें.
  5. onDispose में, findActivity() का इस्तेमाल करके removeOnUserLeaveHintListener जोड़ें और उसे lambda फ़ंक्शन में पास करें.

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")
}

Android 12 के बाद के लिए, छुट्टी वाले ऐप्लिकेशन पर पीआईपी (पिक्चर में पिक्चर) जोड़ें

Android 12 के बाद, PictureInPictureParams.Builder को कार्रवाई बदलने वाली कुंजी जो ऐप्लिकेशन के वीडियो प्लेयर को भेजी जाती है.

  1. एक modifier बनाएं और उस पर onGloballyPositioned को कॉल करें. लेआउट निर्देशांकों का इस्तेमाल बाद के चरण में किया जाएगा.
  2. PictureInPictureParams.Builder() के लिए वैरिएबल बनाएं.
  3. if स्टेटमेंट जोड़कर देखें कि एसडीके टूल S या इसके बाद का वर्शन है या नहीं. अगर ऐसा है, तो जोड़ें setAutoEnterEnabled को बिल्डर पर सेट करें. साथ ही, पीआईपी (पिक्चर में पिक्चर) में जाने के लिए, इसे true पर सेट करें स्वाइप करने पर एक मोड दिखाई देगा. इससे प्रोसेस करने के बजाय आसान ऐनिमेशन मिलता है enterPictureInPictureMode.
  4. setPictureInPictureParams() को कॉल करने के लिए findActivity() का इस्तेमाल करें. build() को इस पर कॉल करें builder और उसे पास कर दें.

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)

बटन की मदद से पीआईपी (पिक्चर में पिक्चर) जोड़ें

बटन पर क्लिक करके पीआईपी मोड में जाने के लिए, 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 मोड में है या नहीं. Google Analytics 4 पर माइग्रेट करने के लिए, 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
    }
}

अब 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()
}

यह पक्का करें कि आपका ऐप्लिकेशन सही समय पर पीआईपी मोड में जाए

इन स्थितियों में आपके ऐप्लिकेशन को पीआईपी मोड में नहीं जाना चाहिए:

  • अगर वीडियो रुक गया है या रुका हुआ है.
  • अगर आप ऐप्लिकेशन के वीडियो प्लेयर के बजाय किसी दूसरे पेज पर हैं.

आपका ऐप्लिकेशन पीआईपी मोड में कब चले, इसे कंट्रोल करने के लिए, स्थिति को ट्रैक करने वाला वैरिएबल जोड़ें mutableStateOf का इस्तेमाल करके, वीडियो प्लेयर की स्क्रीन पर दिखेगी.

वीडियो चल रहा है या नहीं, इसके हिसाब से टॉगल की स्थिति

वीडियो प्लेयर चल रहा है या नहीं, इस आधार पर स्थिति को टॉगल करने के लिए, लिसनर जोड़ें वीडियो प्लेयर. अपने स्टेट वैरिएबल की स्थिति को इस आधार पर टॉगल करें कि प्लेयर चल रहा है या नहीं:

player.addListener(object : Player.Listener {
    override fun onIsPlayingChanged(isPlaying: Boolean) {
        shouldEnterPipMode = isPlaying
    }
})

प्लेयर रिलीज़ हुआ है या नहीं, इसके हिसाब से टॉगल की स्थिति

प्लेयर के रिलीज़ होने पर, अपने स्टेट वैरिएबल को false पर सेट करें:

fun releasePlayer() {
    shouldEnterPipMode = false
}

पीआईपी मोड (Android 12 से पहले) का इस्तेमाल किया गया है या नहीं, यह तय करने के लिए स्टेटस का इस्तेमाल करें

  1. Android 12 से पहले के वर्शन में, PiP जोड़ने के लिए DisposableEffect का इस्तेमाल किया जाता है. इसलिए, आपको rememberUpdatedState की मदद से एक नया वैरिएबल बनाना होगा. साथ ही, newValue को स्टेट वैरिएबल के तौर पर सेट करना होगा. इससे यह पक्का होगा कि अपडेट किए गए वर्शन का इस्तेमाल, 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")
    }

पीआईपी मोड इस्तेमाल किया गया है या नहीं, यह तय करने के लिए स्टेटस का इस्तेमाल करें (Android 12 के बाद)

अपने स्टेट वैरिएबल को setAutoEnterEnabled में पास करें, ताकि आपका ऐप्लिकेशन सिर्फ़ पीआईपी मोड सही समय पर:

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 एपीआई की मदद से, पीआईपी (पिक्चर में पिक्चर) में आसानी से ऐनिमेशन जोड़ा जा सकता है मोड. Android 12 और इसके बाद के वर्शन वाले डिवाइसों पर, पीआईपी मोड से बाहर निकलने पर बेहतर ऐनिमेशन भी मिलता है. इस एपीआई को पीआईपी बिल्डर में जोड़ें, ताकि यह बताया जा सके कि गतिविधि किस इलाके में की जा रही है पीआईपी में ट्रांज़िशन के बाद दिखेगा.

  1. setSourceRectHint() को builder में सिर्फ़ तब जोड़ें, जब राज्य से ऐप्लिकेशन को पीआईपी मोड में जाना होगा. इससे, ऐप्लिकेशन को होने वाले sourceRect का हिसाब लगाने से बचा जा सकता है पीआईपी (पिक्चर में पिक्चर) की सुविधा डालना ज़रूरी नहीं है.
  2. sourceRect वैल्यू को सेट करने के लिए, दिए गए layoutCoordinates का इस्तेमाल करें कार्रवाई बदलने वाली कुंजी पर onGloballyPositioned फ़ंक्शन से.
  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 का इस्तेमाल करना

पीआईपी विंडो का आसपेक्ट रेशियो (लंबाई-चौड़ाई का अनुपात) सेट करने के लिए, इनमें से कोई एक विकल्प चुनें आसपेक्ट रेशियो (लंबाई-चौड़ाई का अनुपात) या प्लेयर के वीडियो के साइज़ की चौड़ाई और ऊंचाई का इस्तेमाल करें. अगर आप Media3 प्लेयर का इस्तेमाल करके, जांच लें कि प्लेयर खाली नहीं है और प्लेयर का आसपेक्ट रेशियो (लंबाई-चौड़ाई का अनुपात) सेट करने से पहले, वीडियो का साइज़ VideoSize.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)

अगर कस्टम प्लेयर का इस्तेमाल किया जा रहा है, तो प्लेयर की ऊंचाई के हिसाब से आसपेक्ट रेशियो (लंबाई-चौड़ाई का अनुपात) सेट करें आपके प्लेयर के खास सिंटैक्स का इस्तेमाल करके, उसकी चौड़ाई और चौड़ाई को सेट करने की सुविधा मिलती है. ध्यान रखें कि अगर आपका प्लेयर शुरू होने के दौरान, आसपेक्ट रेशियो की मान्य सीमाओं से बाहर का साइज़ ले लेता है, तो आपका ऐप्लिकेशन क्रैश हो जाएगा. आपको चेक जोड़ना पड़ सकता है जब आसपेक्ट रेशियो (लंबाई-चौड़ाई का अनुपात) कैलकुलेट किया जा सकता है, जैसा कि Media3 में किया जाता है प्लेयर.

रिमोट ऐक्शन जोड़ना

अगर आपको पीआईपी विंडो में कंट्रोल (चलाएं, रोकें वगैरह) जोड़ने हैं, तो हर उस कंट्रोल के लिए 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. अपनी पिन किए गए वीडियो की विंडो में मौजूद कंट्रोल के लिए, 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)

अगले चरण

इस गाइड में, आपने कंपोज़ में पीआईपी को जोड़ने के सबसे सही तरीकों के बारे में बताया है Android 12 से पहले और Android 12 के बाद वाले वर्शन पर.