Bild im Bild (BiB) mit einem Videoplayer zum Erstellen einer App in Apps einbinden

Bild im Bild (BiB) ist eine spezielle Art des Mehrfenstermodus, der hauptsächlich für die Videowiedergabe verwendet wird. Nutzer können sich ein Video in einem kleinen Fenster ansehen, das in einer Ecke des Bildschirms angepinnt ist, während sie zwischen Apps wechseln oder Inhalte auf dem Hauptbildschirm durchsuchen.

BiB nutzt die in Android 7.0 verfügbaren Mehrfenster-APIs, um das angepinnte Video-Overlay-Fenster bereitzustellen. Wenn du BiB in deine App aufnehmen möchtest, musst du deine Aktivität registrieren, in den BiB-Modus wechseln und darauf achten, dass UI-Elemente ausgeblendet sind und die Videowiedergabe fortgesetzt wird, wenn sich die Aktivität im BiB-Modus befindet.

In diesem Leitfaden wird beschrieben, wie Sie BiB in Compose mithilfe einer Compose-Videoimplementierung in Ihre Anwendung einfügen. In der Socialite App können Sie diese Best Practices in der Praxis sehen.

App für BiB einrichten

Führen Sie im Aktivitäts-Tag der Datei AndroidManifest.xml die folgenden Schritte aus:

  1. Füge supportsPictureInPicture hinzu und setze es auf true, um zu deklarieren, dass du BiB in deiner App verwendest.
  2. Fügen Sie configChanges hinzu und setzen Sie es auf orientation|screenLayout|screenSize|smallestScreenSize, um anzugeben, dass Ihre Aktivität Änderungen der Layoutkonfiguration verarbeitet. So wird deine Aktivität nicht neu gestartet, wenn Layoutänderungen während BiB-Modusübergängen auftreten.

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

Führen Sie im Code die folgenden Schritte aus:

  1. Diese Erweiterung auf Context hinzufügen. Sie verwenden diese Erweiterung in diesem Leitfaden mehrmals, um auf die Aktivität zuzugreifen.
    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")
    }

BiB beim Verlassen der App für vor Android 12 hinzufügen

Wenn du BiB für Geräte vor Android 12 hinzufügen möchtest, verwende addOnUserLeaveHintProvider. So fügst du BiB für ältere Versionen als Android 12 hinzu:

  1. Fügen Sie ein Versions-Gatter hinzu, sodass nur in den Versionen O bis R auf diesen Code zugegriffen wird.
  2. Verwende DisposableEffect mit Context als Schlüssel.
  3. Definieren Sie im DisposableEffect das Verhalten, wenn onUserLeaveHintProvider mit einer Lambda-Funktion ausgelöst wird. Rufen Sie in der Lambda-Funktion enterPictureInPictureMode() auf findActivity() auf und übergeben Sie PictureInPictureParams.Builder().build().
  4. Füge addOnUserLeaveHintListener mithilfe von findActivity() hinzu und übergib die Lambda-Funktion.
  5. Füge in onDispose removeOnUserLeaveHintListener mit findActivity() hinzu und übergib die Lambda-Funktion.

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_TAG, "API does not support PiP")
}

BiB beim Verlassen der App nach Android 12 hinzufügen

Nach Android 12 wird PictureInPictureParams.Builder über einen Modifizierer hinzugefügt, der an den Videoplayer der App übergeben wird.

  1. Erstellen Sie eine modifier und rufen Sie onGloballyPositioned darauf auf. Die Layoutkoordinaten werden in einem späteren Schritt verwendet.
  2. Erstellen Sie eine Variable für PictureInPictureParams.Builder().
  3. Fügen Sie eine if-Anweisung hinzu, um zu prüfen, ob das SDK S oder höher ist. Wenn ja, füge dem Builder setAutoEnterEnabled hinzu und setze ihn auf true, um den BiB-Modus beim Wischen aufzurufen. Die Animation ist so flüssiger als mit enterPictureInPictureMode.
  4. Verwenden Sie findActivity(), um setPictureInPictureParams() aufzurufen. Rufen Sie build() im builder auf und übergeben Sie es.

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)

BiB über eine Schaltfläche hinzufügen

Wenn du den BiB-Modus durch Klicken auf eine Schaltfläche aktivieren möchtest, ruf auf findActivity() enterPictureInPictureMode() auf.

Die Parameter wurden bereits durch vorherige Aufrufe von PictureInPictureParams.Builder festgelegt, sodass Sie keine neuen Parameter für den Builder festlegen müssen. Wenn Sie jedoch Parameter beim Klicken ändern möchten, können Sie sie hier festlegen.

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

Die Benutzeroberfläche im BiB-Modus verwenden

Wenn Sie in den BiB-Modus wechseln, erscheint die gesamte Benutzeroberfläche Ihrer Anwendung in das BiB-Fenster, es sei denn, Sie geben an, wie die Benutzeroberfläche im BiB-Modus ein- und ausgeblendet werden soll.

Als Erstes musst du wissen, wann sich deine App im BiB-Modus befindet. Dazu können Sie OnPictureInPictureModeChangedProvider verwenden. Der folgende Code teilt dir mit, ob sich deine App im BiB-Modus befindet.

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

Jetzt können Sie mit rememberIsInPipMode() festlegen, welche UI-Elemente angezeigt werden sollen, wenn die App in den BiB-Modus wechselt:

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

Achte darauf, dass deine App zur richtigen Zeit in den BiB-Modus wechselt

In den folgenden Situationen sollte der BiB-Modus in deiner App nicht aktiviert werden:

  • Gibt an, ob das Video angehalten oder pausiert wird.
  • Wenn du dich auf einer anderen Seite der App als der Videoplayer befindest:

Wenn du steuern möchtest, wann in deiner App der BiB-Modus aktiviert wird, füge eine Variable hinzu, die den Status des Videoplayers mithilfe eines mutableStateOf verfolgt.

Status je nach Wiedergabestatus des Videos wechseln

Wenn Sie den Status je nachdem, ob der Videoplayer wiedergegeben wird, ändern möchten, fügen Sie dem Videoplayer einen Listener hinzu. Schalten Sie den Status der Statusvariablen danach um, ob der Player eine Wiedergabe ausführt oder nicht:

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

Status je nach Veröffentlichung des Players wechseln

Setze die Statusvariable nach der Veröffentlichung des Players auf false:

fun releasePlayer() {
    shouldEnterPipMode = false
}

Mit Status festlegen, ob der BiB-Modus aktiviert wird (vor Android 12)

  1. Da beim Hinzufügen von BiB vor 12 ein DisposableEffect verwendet wird, müssen Sie mit rememberUpdatedState eine neue Variable erstellen, wobei newValue als Statusvariable festgelegt ist. Dadurch wird die aktualisierte Version in DisposableEffect verwendet.
  2. Fügen Sie in der Lambda-Funktion, die das Verhalten beim Auslösen von OnUserLeaveHintListener definiert, eine if-Anweisung mit der Statusvariable um den Aufruf von enterPictureInPictureMode() hinzu:

    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_TAG, "API does not support PiP")
    }

Status verwenden, um festzulegen, ob der BiB-Modus aktiviert wird (nach Android 12)

Übergeben Sie die Statusvariable an setAutoEnterEnabled, damit Ihre Anwendung nur zur richtigen Zeit in den BiP-Modus wechselt:

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)

Mit setSourceRectHint kannst du eine flüssige Animation implementieren

Die setSourceRectHint API sorgt für eine flüssigere Animation für den Wechsel in den BiB-Modus. In Android 12 und höher ist die Animation beim Beenden des BiB-Modus flüssiger. Füge diese API dem BiB-Builder hinzu, um den Bereich der Aktivität anzugeben, der nach der Umstellung auf BiB sichtbar ist.

  1. Füge dem builder nur dann setSourceRectHint() hinzu, wenn der Status definiert, dass die App in den BiB-Modus wechseln soll. Dadurch wird die Berechnung von sourceRect vermieden, wenn die App nicht BiB eingeben muss.
  2. Zum Festlegen des Werts sourceRect verwenden Sie die layoutCoordinates aus der Funktion onGloballyPositioned für den Modifikator.
  3. Rufen Sie setSourceRectHint() für builder auf und übergeben Sie die Variable 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)

Mit setAspectRatio das Seitenverhältnis des BiB-Fensters festlegen

Wenn Sie das Seitenverhältnis des BiB-Fensters festlegen möchten, können Sie entweder ein bestimmtes Seitenverhältnis auswählen oder die Breite und Höhe der Variablen sourceRect verwenden.

val context = LocalContext.current

val pipModifier = modifier.onGloballyPositioned { layoutCoordinates ->
    val builder = PictureInPictureParams.Builder()

    if (shouldEnterPipMode) {
        val sourceRect = layoutCoordinates.boundsInWindow().toAndroidRectF().toRect()
        builder.setSourceRectHint(sourceRect)
        builder.setAspectRatio(
            Rational(sourceRect.width(), sourceRect.height())
        )
    }

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
        builder.setAutoEnterEnabled(shouldEnterPipMode)
    }
    context.findActivity().setPictureInPictureParams(builder.build())
}

VideoPlayer(pipModifier)

Remote-Aktionen hinzufügen

Wenn Sie dem BiB-Fenster Steuerelemente (Wiedergabe, Pause usw.) hinzufügen möchten, müssen Sie für jedes gewünschte Steuerelement ein RemoteAction erstellen.

  1. Fügen Sie Konstanten für die Übertragungssteuerung hinzu:
    // 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. Erstellen Sie eine Liste mit RemoteActions für die Steuerelemente im BiB-Fenster.
  3. Als Nächstes fügen Sie ein BroadcastReceiver hinzu und überschreiben onReceive(), um die Aktionen der einzelnen Schaltflächen festzulegen. Verwenden Sie einen DisposableEffect, um den Empfänger und die Remote-Aktionen zu registrieren. Heben Sie dann die Registrierung des Empfängers auf.
    @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. Übergeben Sie eine Liste Ihrer Remote-Aktionen an den PictureInPictureParams.Builder:
    val context = LocalContext.current
    
    val pipModifier = modifier.onGloballyPositioned { layoutCoordinates ->
        val builder = PictureInPictureParams.Builder()
        builder.setActions(
            listOfRemoteActions()
        )
    
        if (shouldEnterPipMode) {
            val sourceRect = layoutCoordinates.boundsInWindow().toAndroidRectF().toRect()
            builder.setSourceRectHint(sourceRect)
            builder.setAspectRatio(
                Rational(sourceRect.width(), sourceRect.height())
            )
        }
    
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            builder.setAutoEnterEnabled(shouldEnterPipMode)
        }
        context.findActivity().setPictureInPictureParams(builder.build())
    }
    VideoPlayer(modifier = pipModifier)

Nächste Schritte

In diesem Leitfaden haben Sie Best Practices für das Hinzufügen von BiB in Compose in den Versionen vor und nach Android 12 kennengelernt.

  • In der App Socialite finden Sie die Best Practices zum Erstellen von BiB in der Praxis.
  • Weitere Informationen finden Sie in den PiP-Designanleitungen.