Jetpack-Bibliothek „Bild im Bild“ verwenden

Die Jetpack-Bibliothek für Bild im Bild (BiB) bietet Android-App-Entwicklern eine optimierte und robuste Lösung zum Implementieren der BiB-Funktion, insbesondere für Apps zur Medienwiedergabe, Videokommunikation und Navigation. Durch die Bereitstellung einer einheitlichen API hilft die Bibliothek, Boilerplate-Code und häufige In-App-Fehler zu vermeiden und die Qualität der PiP-Nutzererfahrung insgesamt zu verbessern.

Die PiP-Jetpack-Bibliothek erleichtert die Verwendung der vorhandenen PiP-APIs, indem sie mehrere wichtige Herausforderungen und Inkonsistenzen im Android-Ökosystem behebt:

  • Fragmentierung des Betriebssystems: Die Bibliothek verarbeitet automatisch Unterschiede bei PiP-API-Aufrufen in verschiedenen Android-Versionen, z. B. die Verwendung von enterPictureInPictureMode vor Android 12 und isAutoEnterEnabled danach. Entwickler müssen sich also nicht um Versionsunterschiede kümmern.
  • Falsche PiP-Parameter: Es bietet eine einheitliche Lösung, um PiP-Parameter wie setSourceRectHint korrekt festzulegen und so flüssige und hochwertige Animationen während der Medienwiedergabe zu erstellen.
  • Einheitliche PiP-Status-Callbacks: onPictureInPictureModeChanged und onPictureInPictureUiStateChanged werden in einer einzigen, einheitlichen Callback-Schnittstelle (PictureInPictureDelegate.OnPictureInPictureEventListener) zusammengefasst, um die Status- und UI-Verwaltung zu vereinfachen.
  • Reduzierung von Boilerplate-Code: Die Bibliothek reduziert die Menge an sich wiederholendem Boilerplate-Code, indem sie vordefinierte Sätze von RemoteActions für gängige Anwendungsfälle wie Wiedergabesteuerung und Videoanrufaktionen bietet.
  • Zukunftssicherheit: Weitere PiP-Funktionen werden über die Jetpack-Bibliothek bereitgestellt, sodass Nutzer mit minimalem oder gar keinem Aufwand auf zusätzliche Funktionen zugreifen können.

Migrationsworkflow

Ermitteln Sie die Anwendungsfallkategorie und die alte PiP-Logik der App:

Kategorien: Videowiedergabe, Navigation oder Videoanruf.

Ältere PiP-Logik zur Identifizierung:

  • onUserLeaveHint
  • setAutoEnterEnabled
  • onPictureInPictureModeChanged
  • onPictureInPictureUiStateChanged
  • setPictureInPictureParams.

2. AndroidManifest-Konfiguration

Achte darauf, dass die Aktivität, die in den PiP-Modus wechselt, in AndroidManifest.xml mit den erforderlichen configChanges deklariert wird, um unnötige Neustarts zu vermeiden:

<activity
android:name="VideoActivity" android:supportsPictureInPicture="true"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation">
</activity>

3. Umgebung einrichten

Fügen Sie die erforderlichen Abhängigkeiten zu build.gradle hinzu:

dependencies {
implementation("androidx.core:core:1.18.0")
implementation("androidx.activity:activity:1.13.0")
implementation("androidx.core:core-pip:1.0.0-alpha02") }

Verwende die neuesten AndroidX-Bibliotheken für die Abhängigkeiten und sieh dir die Seite Releases an, um diese Informationen zu erhalten.

4. Vorlagenauswahl und Initialisierung

Wählen Sie die Implementierungsvorlage aus, die am besten zum Anwendungsfall der App passt:

  • Navigation und Videoanruf: BasicPictureInPicture; die nahtlose Größenanpassung wird in der Regel nicht unterstützt und Sie benötigen keinen Hinweis auf das Quellrechteck.
  • Videowiedergabe: VideoPlaybackPictureInPicture; verfolgt automatisch die Grenzen der Player-Ansicht für den Hinweis zum Quellrechteck und ermöglicht standardmäßig eine nahtlose Größenänderung.

Wenn Sie die Jetpack-Bibliothek verwenden möchten, ersetzen Sie Ihre vorhandene benutzerdefinierte PiP-Implementierung durch die Jetpack-Bibliotheks-APIs. Die Komplexität und die Kosten der Einführung hängen von der aktuellen Implementierung der App ab.

In den folgenden Abschnitten werden einige typische Anwendungsfälle von PiP und die erforderlichen Implementierungsschritte beschrieben:

Die App informiert die Bibliothek über den aktiven oder inaktiven Status der Navigation und legt das Seitenverhältnis fest. Den Rest übernimmt die Jetpack-Bibliothek.

Wichtige Unterschiede

  1. Es ist nicht erforderlich, auf App-Seite zwischen automatischem und herkömmlichem Betreten zu unterscheiden.
  2. Konsolidierte Callback-Schnittstellen.
  3. Neuer PictureInPictureParams-Builder für Abwärtskompatibilität.

Videoanruf

Die App informiert die Bibliothek über den aktiven oder inaktiven Status des Anrufs und legt das Seitenverhältnis fest.

Wichtige Unterschiede

  1. Es ist nicht erforderlich, auf App-Seite zwischen automatischem und herkömmlichem Betreten zu unterscheiden.
  2. Konsolidierte Callback-Schnittstellen.
  3. Neuer PictureInPictureParams-Builder für Abwärtskompatibilität.
  4. Standardisierte Aktionssymbole für Videoanrufe.

5. Codemigration

  • Einstiegslogik:Ersetze API-spezifische Logik wie setAutoEnterEnabled für Android 12 und höher oder onUserLeaveHint für Android 11 und niedriger durch setEnabled. Lösen Sie dies immer aus, wenn sich der PiP-Berechtigungsstatus ändert.
  • Callbacks: onPictureInPictureModeChanged (Layoutwechsel) und onPictureInPictureUiStateChanged (Animation/Zustände) werden in einem einheitlichen ereignisbasierten Callback onPictureInPictureEvent zusammengefasst.
  • Aktionen und Parameter: Aktualisieren Sie Parameter mit setActions und setAspectRatio in der Vorlageninstanz, wenn sie sich ändern.

Referenzimplementierungsmuster

Beispiele für Implementierungen.

Navigation und Videoanruf

class NavOrVideoCallJpipActivity : ComponentActivity(), PictureInPictureDelegate.OnPictureInPictureEventListener {
    private lateinit var pictureInPictureImpl: BasicPictureInPicture
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        pictureInPictureImpl = BasicPictureInPicture(this)
        // BasicPictureInPicture is ideal for Navigation and Video call use cases.
        pictureInPictureImpl.addOnPictureInPictureEventListener(
            ContextCompat.getMainExecutor(this),
            this
        )
        setContent {
        }
    }
    override fun onPictureInPictureEvent(
        event: PictureInPictureDelegate.Event,
        config: Configuration?
    ) {
        when (event) {
            PictureInPictureDelegate.Event.ENTERED -> { /* Toggle to PiP layout */ }
            PictureInPictureDelegate.Event.EXITED -> { /* Toggle to Full-screen layout */ }
            PictureInPictureDelegate.Event.STASHED -> { /* Optional: PiP is stashed */ }
            PictureInPictureDelegate.Event.UNSTASHED -> { /* Optional: PiP is unstashed */ }
        }
    }
}

Videowiedergabe

class VideoPlaybackJpipActivity : ComponentActivity(), PictureInPictureDelegate.OnPictureInPictureEventListener {
    private lateinit var pictureInPictureImpl: VideoPlaybackPictureInPicture
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        pictureInPictureImpl = VideoPlaybackPictureInPicture(this)
        pictureInPictureImpl.addOnPictureInPictureEventListener(
            ContextCompat.getMainExecutor(this),
            this
        )
        setContent {
            ContentScreen(pictureInPictureImpl)
        }
    }
    override fun onPictureInPictureEvent(
        event: PictureInPictureDelegate.Event,
        config: Configuration?
    ) {
        when (event) {
            PictureInPictureDelegate.Event.ENTER_ANIMATION_START -> { /* Hide overlays */ }
            PictureInPictureDelegate.Event.ENTER_ANIMATION_END -> { /* Animation finished */ }
            PictureInPictureDelegate.Event.ENTERED -> { /* Switch to PiP layout */ }
            PictureInPictureDelegate.Event.STASHED -> { /* PiP stashed */ }
            PictureInPictureDelegate.Event.UNSTASHED -> { /* PiP unstashed */ }
            PictureInPictureDelegate.Event.EXITED -> { /* Return to full-screen */ }
        }
    }

    @Composable
    fun ContentScreen(pipController: VideoPlaybackPictureInPicture) {
        DisposableEffect(pipController) {
            onDispose {
                pipController.close()
            }
        }
    }
}