Jetpack-Bibliothek „Bild im Bild“ verwenden

Die Jetpack-Bibliothek für Bild-im-Bild (BiB) bietet eine optimierte und robuste Lösung für Android-App-Entwickler, um die BiB-Funktionalität zu implementieren, insbesondere für Apps zur Medienwiedergabe, Videokommunikation und Navigation. Durch die Bereitstellung einer einheitlichen API trägt die Bibliothek dazu bei, Boilerplate-Code und häufige In-App-Fehler zu vermeiden und die Gesamtqualität der PiP-Benutzerfreundlichkeit zu verbessern.

Die PiP-Jetpack-Bibliothek vereinfacht die vorhandenen PiP-APIs, indem sie mehrere wichtige Herausforderungen und Inkonsistenzen im Android-Ökosystem angeht:

  • OS-Fragmentierung: 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 BiB-Parameter: Es bietet eine einheitliche Lösung zum korrekten Festlegen von BiB-Parametern, z. B. setSourceRectHint, um während der Medienwiedergabe flüssige und hochwertige Animationen 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.
  • Weniger Boilerplate-Code: Die Bibliothek reduziert die Menge an sich wiederholendem Boilerplate-Code, indem sie vordefinierte Sets von RemoteActions für gängige Anwendungsfälle wie Wiedergabesteuerung und Videoanrufaktionen bietet.
  • Zukunftssicherheit: Weitere BiB-Funktionen werden über die Jetpack-Bibliothek bereitgestellt, sodass Nutzer mit minimalem bis gar keinem Aufwand auf zusätzliche Funktionen zugreifen können.

Migrationsworkflow

Kategorie des Anwendungsfalls und alte PiP-Logik der App ermitteln:

Kategorien:Videowiedergabe, Navigation oder Videoanruf.

Alte PiP-Logik zur Identifizierung:

  • onUserLeaveHint
  • setAutoEnterEnabled
  • onPictureInPictureModeChanged
  • onPictureInPictureUiStateChanged
  • setPictureInPictureParams.

2. AndroidManifest-Konfiguration

Achte darauf, dass die Aktivität, die in den PiP-Modus wechselt, die Unterstützung in AndroidManifest.xml mit dem erforderlichen configChanges deklariert, 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 build.gradle die erforderlichen Abhängigkeiten 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") }

Verwenden Sie die neuesten AndroidX-Bibliotheken für die Abhängigkeiten. Informationen dazu finden Sie auf der Seite Releases.

4. Vorlage auswählen und initialisieren

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

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

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

In den folgenden Abschnitten werden einige typische Anwendungsfälle für den Bild-im-Bild-Modus und die erforderlichen Implementierungsschritte beschrieben:

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

Wichtige Unterschiede

  1. Auf App-Seite muss nicht zwischen der automatischen Eingabe und der alten Eingabe unterschieden werden.
  2. Konsolidierte Callback-Schnittstellen.
  3. Neuer PictureInPictureParams-Builder für die 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. Auf App-Seite muss nicht zwischen der automatischen Eingabe und der alten Eingabe unterschieden werden.
  2. Konsolidierte Callback-Schnittstellen.
  3. Neuer PictureInPictureParams-Builder für die Abwärtskompatibilität.
  4. Standardisierte Aktionssymbole für Videoanrufe.

5. Codemigration

  • Eingangslogik:Ersetzen Sie API-spezifische Logik wie setAutoEnterEnabled für Android 12 und höher oder onUserLeaveHint für Android 11 und niedriger durch setEnabled. Löse diesen Trigger immer dann aus, wenn sich der Status der Eignung für den BiB-Modus ändert.
  • Callbacks:onPictureInPictureModeChanged (Layout-Umschaltung) und onPictureInPictureUiStateChanged (Animation/Status) werden in einem einheitlichen ereignisbasierten Callback onPictureInPictureEvent zusammengefasst.
  • Aktionen und Parameter:Aktualisieren Sie Parameter mit setActions und setAspectRatio in der Vorlageninstanz, sobald sie sich ändern.
  • Sonderbehandlung von Videos:Verwenden Sie für Video-Apps setPlayerView, um die Aktualisierung von Quellrechteck-Hinweisen zu automatisieren und für reibungslose Übergänge zu sorgen. ` ### 6. Bereinigen

Rufen Sie für VideoPlaybackPictureInPicture close in onDispose oder onDestroy auf, um Ressourcen wie View-Tracker freizugeben.

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