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

Der Bild-im-Bild-Modus (PiB) ist ein spezieller Mehrfenstermodus, der hauptsächlich für die Videowiedergabe verwendet wird. Nutzer können sich ein Video in einem kleinen Fenster ansehen, das an 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 angepinnten Video-Overlay-Fenster. Um BiB zu deiner App hinzuzufügen, musst du deine aktualisiere deine Aktivitäten nach Bedarf in den BiB-Modus und achte darauf, dass UI-Elemente werden ausgeblendet und die Videowiedergabe wird fortgesetzt, wenn sich die Aktivität im BiB-Modus befindet.

In dieser Anleitung wird beschrieben, wie du deiner App BiB in „Compose“ mit einem Video des Typs „Verfassen“ hinzufügen kannst Implementierung. Sieh dir diese besten Produkte in der App Socialite an. in der Praxis anwenden.

BiB für deine App einrichten

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

  1. Fügen Sie supportsPictureInPicture hinzu und setzen Sie es auf true, um zu erklären, dass Sie mit BiB in Ihrer App nutzen.
  2. configChanges hinzufügen und festlegen auf orientation|screenLayout|screenSize|smallestScreenSize, um anzugeben, dass verarbeitet Ihre Aktivität Änderungen der Layoutkonfiguration. Auf diese Weise werden Ihre Aktivitäten wird nicht neu gestartet, wenn Layoutänderungen bei Übergängen im BiB-Modus erfolgen.

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

Gehen Sie in Ihrem Compose-Code so vor:

  1. Diese Erweiterung auf Context hinzufügen. Sie verwenden diese Erweiterung im Laufe des Leitfadens 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 ältere Versionen vor Android 12 hinzufügen

Wenn du BiB für Nutzer vor Android 12 hinzufügen möchtest, verwende addOnUserLeaveHintProvider. Folgen So fügst du BiB für Nutzer vor Android 12 hinzu:

  1. Fügen Sie ein Versionsgate hinzu, sodass auf diesen Code nur in den Versionen O bis R zugegriffen wird.
  2. Verwenden Sie DisposableEffect mit Context als Schlüssel.
  3. Definieren Sie in DisposableEffect das Verhalten für den onUserLeaveHintProvider wird durch eine Lambda-Funktion ausgelöst. Rufen Sie in der Lambda-Funktion enterPictureInPictureMode() am findActivity() und übergeben Sie die Karte PictureInPictureParams.Builder().build()
  4. Füge mit findActivity() addOnUserLeaveHintListener hinzu und übergib die Lambda-Funktion.
  5. Fügen Sie in onDispose mit findActivity() removeOnUserLeaveHintListener hinzu und die Lambda übergeben.

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

PiP-Unterstützung für den App-Hintergrund für Android 12 und höher hinzufügen

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

  1. Erstellen Sie eine modifier und rufen Sie onGloballyPositioned darauf auf. Das Layout werden in einem späteren Schritt verwendet.
  2. Erstellen Sie eine Variable für PictureInPictureParams.Builder().
  3. Füge eine if-Anweisung hinzu, um zu prüfen, ob das SDK S oder höher ist. Wenn ja, fügen Sie setAutoEnterEnabled an den Builder und legen Sie ihn auf true fest, um BiB zu verwenden wenn Sie wischen. Dadurch wird eine flüssigere Animation erzielt als bei der Verwendung von enterPictureInPictureMode.
  4. findActivity() verwenden, um setPictureInPictureParams() aufzurufen. build() anrufen unter und übergeben es an die 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)

BiB über eine Schaltfläche hinzufügen

Um in den BiB-Modus über eine Schaltfläche zu wechseln, rufen Sie enterPictureInPictureMode() am findActivity().

Die Parameter wurden bereits durch vorherige Aufrufe der PictureInPictureParams.Builder, Sie müssen also keine neuen Parameter festlegen. für den Builder. Wenn Sie jedoch die Parameter der Schaltfläche 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!")
}

UI im BiB-Modus verwalten

Im BiB-Modus erscheint die gesamte Benutzeroberfläche Ihrer App im BiB-Fenster, es sei denn, Sie wie deine UI im BiB-Modus und aus dem BiB-Modus aussehen soll.

Zuerst müssen Sie wissen, ob sich Ihre App im BiB-Modus befindet. Sie können OnPictureInPictureModeChangedProvider. Der folgende Code gibt an, ob sich Ihre 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 PiP-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()
}

Sorge dafür, dass deine App zur richtigen Zeit in den BiB-Modus wechselt.

In den folgenden Situationen sollte Ihre App nicht in den BiB-Modus wechseln:

  • Wenn das Video angehalten oder pausiert ist.
  • Sie befinden sich auf einer anderen Seite der App als der Videoplayer.

Wenn du festlegen möchtest, wann deine App in den BiB-Modus wechselt, füge eine Variable hinzu, die den Status verfolgt des Videoplayers mit mutableStateOf.

Status abhängig davon wechseln, ob das Video abgespielt wird

Zum Umschalten des Status abhängig davon, ob der Videoplayer abgespielt wird, fügen Sie einen Listener auf Videoplayer. Ändern Sie den Status der Statusvariablen je nachdem, ob der Spieler gerade spielt oder nicht:

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

Status je nach Veröffentlichung des Players wechseln

Wenn der Player freigegeben wird, setze deine Statusvariable auf false:

fun releasePlayer() {
    shouldEnterPipMode = false
}

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

  1. Da beim Hinzufügen von BiB vor 12 ein DisposableEffect verwendet wird, musst du ein eine neue Variable von rememberUpdatedState, wobei newValue als Ihr state-Variable auf. So wird sichergestellt, dass die aktualisierte Version im DisposableEffect
  2. In der Lambda, die das Verhalten definiert, wenn das OnUserLeaveHintListener ausgelöst wird, fügen Sie eine if-Anweisung mit der Zustandsvariable um den Aufruf von 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")
    }

Mit dem Status definieren, ob der BiB-Modus aktiviert wird (ab Android 12)

Übergeben Sie Ihre Zustandsvariable an setAutoEnterEnabled, sodass Ihre App nur BiB-Modus zur richtigen Zeit:

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 eine flüssige Animation implementieren

Die setSourceRectHint API erstellt eine flüssigere Animation bei der Eingabe von BiB . Unter Android 12 und höher wird außerdem eine flüssigere Animation beim Beenden des PiP-Modus erstellt. Fügen Sie diese API dem PiP-Builder hinzu, um den Bereich der Aktivität anzugeben, der nach dem Wechsel zu PiP sichtbar ist.

  1. Fügen Sie dem builder nur dann setSourceRectHint() hinzu, wenn der Bundesstaat Folgendes definiert: App in den BiB-Modus wechseln. Dadurch wird vermieden, dass sourceRect berechnet wird, wenn die App BiB nicht eingegeben werden muss.
  2. Um den Wert sourceRect festzulegen, verwenden Sie die angegebenen layoutCoordinates aus der Funktion onGloballyPositioned für den Modifikator.
  3. Rufe setSourceRectHint() im builder auf und übergebe 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

Um das Seitenverhältnis des BiB-Fensters festzulegen, können Sie entweder ein bestimmtes Seitenverhältnis oder die Breite und Höhe der Videogröße des Players an. Wenn Sie dass Sie einen media3-Player verwenden, vergewissern Sie sich, dass der Wert nicht null ist und dass die Videogröße nicht gleich VideoSize.UNKNOWN ist, bevor Sie das Seitenverhältnis festlegen Seitenverhältnis.

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)

Wenn du einen benutzerdefinierten Player verwendest, lege das Seitenverhältnis auf der Höhe des Players fest. und Breite unter Verwendung der für Ihren Player spezifischen Syntax festlegen. Wenn Ihr Spieler ändert seine Größe während der Initialisierung, wenn sie außerhalb der gültigen Grenzen von oder das Seitenverhältnis haben, stürzt die App ab. Eventuell müssen Sie im Bereich wann das Seitenverhältnis berechnet werden kann, ähnlich wie bei einem „media“.3 Player.

Fernaktionen hinzufügen

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

  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. Erstelle eine Liste von RemoteActions für die Steuerelemente in deinem BiB-Fenster.
  3. Fügen Sie als Nächstes einen BroadcastReceiver hinzu und überschreiben Sie onReceive(), um den Aktionen der einzelnen Schaltflächen. Verwenden Sie DisposableEffect, um den Empfänger und die Remote-Aktionen zu registrieren. Wenn der Player entsorgt wird, hebe die Registrierung des Empfänger.
    @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 && 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)

Nächste Schritte

In diesem Leitfaden haben Sie die Best Practices zum Hinzufügen von PiP in Compose sowohl vor als auch nach Android 12 kennengelernt.