Oluştur video oynatıcısıyla uygulamanıza pencere içinde pencere (PIP) ekleme

Pencere içinde pencere (PIP), çoğunlukla aşağıdakilerden hangisi için kullanılan özel bir çoklu pencere modu türüdür: Video oynatma. Kullanıcının bir videoyu sabitlenmiş küçük bir pencerede izlemesini sağlar. uygulamalar arasında gezinirken veya ana ekrana dokunun.

PiP, sabitlenmiş video yer paylaşımı penceresini sağlamak için Android 7.0'da kullanıma sunulan çok pencereli API'lerden yararlanır. Uygulamanıza PIP eklemek için açın, etkinliğinizi gerektiği şekilde PiP moduna geçirin ve kullanıcı arayüzü öğelerinin etkinlik PiP modundayken video oynatmaya devam eder.

Bu kılavuzda, Compose'da PiP özelliğini bir Compose videosuyla uygulamanıza nasıl ekleyeceğiniz açıklanmaktadır. bazı ipuçları vereceğim. En iyilerini görmek için Socialite uygulamasına göz atın bazı ipuçları vereceğim.

Uygulamanızı PiP için ayarlama

AndroidManifest.xml dosyanızın etkinlik etiketinde aşağıdakileri yapın:

  1. supportsPictureInPicture adlı oyuncuyu ekleyip true olarak ayarlayarak en iyi uygulamaları paylaşacağız.
  2. configChanges ekle ve şu şekilde ayarla belirtmek için orientation|screenLayout|screenSize|smallestScreenSize etkinliğiniz düzen yapılandırma değişikliklerini nasıl işler? Böylece etkinliğiniz PiP modu geçişleri sırasında düzen değişiklikleri olduğunda yeniden başlatılmıyor.

      <activity
        android:name=".SnippetsActivity"
        android:exported="true"
        android:supportsPictureInPicture="true"
        android:configChanges="orientation|screenLayout|screenSize|smallestScreenSize"
        android:theme="@style/Theme.Snippets">

Oluşturma kodunuzda aşağıdakileri yapın:

  1. Bu uzantıyı Context sitesinde ekleyin. Etkinliğe erişmek için rehber boyunca bu uzantıyı birden çok kez kullanacaksınız.
    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 öncesi için izinde PiP uygulamasını ekleyin

Android 12 öncesi sürümlerde PIP eklemek için addOnUserLeaveHintProvider uygulamasını kullanın. Takip et aşağıdaki adımları uygulayarak Android 12 öncesi için PiP ekleyebilirsiniz:

  1. Bu koda yalnızca O sürümlerinden R tarihine kadar erişilebilmesi için bir sürüm kapısı ekleyin.
  2. Anahtar olarak Context içeren bir DisposableEffect kullanın.
  3. DisposableEffect içinde onUserLeaveHintProvider bir lambda kullanılarak tetiklendi. Lambda'da şunu ara: enterPictureInPictureMode() (findActivity()) ve geçip geçme PictureInPictureParams.Builder().build().
  4. findActivity() kullanarak addOnUserLeaveHintListener ekleyip lambda'yı geçin.
  5. onDispose uygulamasında findActivity() ile removeOnUserLeaveHintListener ekleyin ve lambda'dan iniyoruz.

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 sonrası için izin verilen uygulamaya PiP'yi ekleme

Android 12'den sonra PictureInPictureParams.Builder, değiştiricisi kullanabilirsiniz.

  1. Bir modifier oluşturup onGloballyPositioned'i çağırın. Düzen koordinatlar sonraki bir adımda kullanılacaktır.
  2. PictureInPictureParams.Builder() için bir değişken oluşturun.
  3. SDK'nın S veya daha yeni bir sürüm olup olmadığını kontrol etmek için bir if beyanı ekleyin. Varsa PiP'ye girmek için oluşturucuya setAutoEnterEnabled ve true olarak ayarlayın devam edebilir. Bu yöntem, ekran görüntüsüne kıyasla daha akıcı bir animasyon sağlar. enterPictureInPictureMode.
  4. setPictureInPictureParams() numaralı telefonu aramak için findActivity() numarasını kullanın. builder'da build()'yi arayın ve bu numarayı iletin.

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)

Tek düğmeyle PIP ekleyin

PiP moduna düğme tıklamasıyla girmek için findActivity()'da enterPictureInPictureMode()'ı arayın.

Parametreler, PictureInPictureParams.Builder için yapılan önceki çağrılarla zaten ayarlanmıştır. Bu nedenle, oluşturucuda yeni parametreler ayarlamanıza gerek yoktur. Ancak, düğmedeki parametreleri değiştirmek isterseniz buradan ayarlayabilirsiniz.

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 modunda kullanıcı arayüzünü yönetme

PiP moduna girdiğinizde uygulamanızın kullanıcı arayüzünün tamamı PiP penceresine girer. kullanıcı arayüzünüzün PiP modunda nasıl ve bu modda görünmesi gerektiğini belirtin.

Öncelikle, uygulamanızın PiP modunda olup olmadığını öğrenmeniz gerekir. Tekliflerinizi otomatikleştirmek ve optimize etmek için Bunu başarmak için OnPictureInPictureModeChangedProvider. Aşağıdaki kod, uygulamanızın PiP modunda olup olmadığını gösterir.

@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
    }
}

Artık hangi kullanıcı arayüzü öğelerinin gösterileceğini değiştirmek için rememberIsInPipMode() öğesini kullanabilirsiniz Uygulama PiP moduna girdiğinde:

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

Uygulamanızın doğru zamanlarda PiP moduna girdiğinden emin olun

Uygulamanız aşağıdaki durumlarda PiP moduna girmemelidir:

  • Video durdurulmuş veya duraklatılmışsa.
  • Uygulamanın video oynatıcıdan farklı bir sayfasındaysanız.

Uygulamanızın PiP moduna ne zaman gireceğini kontrol etmek için durumu izleyen bir değişken ekleyin mutableStateOf kullanarak video oynatıcının.

Videonun oynatılıp oynatılmamasına göre durumu değiştirme

Durumu, video oynatıcının oynayıp oynamadığına göre değiştirmek için video oynatıcıya bir dinleyici ekleyin. Oynatıcının durumuna göre durum değişkeninizin durumunu değiştirin çalıp çalmıyor mu?

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

Oynatıcının serbest olup olmamasına göre durumu değiştir

Oynatıcı serbest bırakıldığında durum değişkeninizi false olarak ayarlayın:

fun releasePlayer() {
    shouldEnterPipMode = false
}

PiP moduna girilip girilmediğini tanımlamak için durumu kullanın (Android 12'den önceki sürümler)

  1. PiP'yi 12 sürümünden önce eklemek için DisposableEffect kullanılır. Bu nedenle, newValue durum değişkeniniz olarak ayarlayarak rememberUpdatedState ile yeni bir değişken oluşturmanız gerekir. Bu işlem, güncellenmiş sürümün DisposableEffect içinde kullanılmasını sağlar.
  2. OnUserLeaveHintListener çalıştırıldığında davranışı tanımlayan lambdada tetiklendiğinde, durum değişkenine sahip bir if ifadesi ekleyin. enterPictureInPictureMode():

    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 moduna girilip girilmediğini tanımlamak için durum özelliğini kullanma (Android 12'den sonra)

Uygulamanızın yalnızca şunu girmesi için durum değişkeninizi setAutoEnterEnabled öğesine iletin: Doğru zamanda PiP modu:

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)

Akıcı bir animasyon uygulamak için setSourceRectHint kullanın

setSourceRectHint API, PiP'ye girmek için daha akıcı bir animasyon oluşturur. yatırım yapmanız önemlidir. Bu özellik, Android 12 ve sonraki sürümlerde PiP modundan çıkmak için daha akıcı bir animasyon oluşturur. PiP oluşturma aracına bu API'yi ekleyerek etkinliğin ait olduğu alanı görünür olması gerekir.

  1. setSourceRectHint() öğesini yalnızca builder öğesine PiP moduna geçmelidir. Bu sayede, uygulamanın PiP'ye girmesi gerekmediğinde sourceRect hesaplanması önlenir.
  2. sourceRect değerini ayarlamak için, verilen layoutCoordinates kullanın. değiştiricideki onGloballyPositioned işlevinden seçebilirsiniz.
  3. builder üzerinde setSourceRectHint() adlı kişiyi arayın ve sourceRect içinde geçiş yapın değişkenine eklenmelidir.

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 penceresinin en boy oranını ayarlamak için setAspectRatio tuşunu kullanın

PiP penceresinin en boy oranını ayarlamak için belirli bir veya oynatıcının video boyutunun genişliğini ve yüksekliğini kullanabilirsiniz. Şu durumda: medya 3 oynatıcı kullanarak, oynatıcının boş olmadığından ve oynatıcının en boy oranı ayarlanmadan önce video boyutu VideoSize.UNKNOWN değerine eşit değil oranı.

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)

Özel bir oynatıcı kullanıyorsanız en boy oranını oynatıcının yüksekliğinde ayarlayın oynatıcınıza özel söz dizimini kullanarak genişliği ayarlayın. Oynatıcınızın yalnızca yeniden boyutlandırır. Başlatma sırasında, en boy oranı olursa uygulamanız kilitlenir. Media3 oynatıcıda olduğu gibi, en boy oranının ne zaman hesaplanabileceğiyle ilgili kontroller eklemeniz gerekebilir.

Uzaktan işlem ekle

PiP pencerenize kontroller (oynatma, duraklatma vb.) eklemek istiyorsanız bir Eklemek istediğiniz her kontrol için RemoteAction.

  1. Yayın kontrolleriniz için sabit değerler ekleyin:
    // 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 pencerenizdeki kontroller için RemoteActions listesi oluşturun.
  3. Daha sonra, bir BroadcastReceiver ekleyin veonReceive() otomatik olarak kontrol edilir. Bir DisposableEffect kullanarak uzaktan yapılan işlemlerdir. Oynatıcı atıldığında, kaydı iptal et alıcı.
    @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. Uzaktan gerçekleştirdiğiniz işlemlerin listesini 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)

Sonraki adımlar

Bu kılavuzda, Compose'da PIP eklemeyle ilgili en iyi uygulamaları Android 12 öncesi ve Android 12 sonrası sürümler.