Menambahkan video menggunakan picture-in-picture (PiP)

Mulai dari Android 8.0 (API level 26), Android memungkinkan aktivitas diluncurkan dalam mode picture-in-picture (PiP). PiP adalah jenis khusus mode multi-aplikasi yang sebagian besar digunakan untuk pemutaran video. Dengan fitur ini, pengguna dapat menonton video di jendela kecil yang disematkan ke sudut layar sambil beralih antar-aplikasi atau menjelajahi konten di layar utama.

PiP memanfaatkan API multi-aplikasi yang tersedia di Android 7.0 untuk menyediakan jendela overlay video yang disematkan. Untuk menambahkan PiP ke aplikasi, Anda harus mendaftarkan aktivitas yang mendukung PiP, mengalihkan aktivitas ke mode PiP sesuai kebutuhan, dan memastikan elemen UI disembunyikan dan pemutaran video berlanjut saat aktivitas berlangsung dalam mode PiP.

Jendela PiP akan muncul di lapisan paling atas pada layar, di sudut yang dipilih oleh sistem.

Cara pengguna dapat berinteraksi dengan jendela PiP

Pengguna dapat menarik jendela PiP ke lokasi lain. Mulai Android 12, pengguna juga dapat:

  • Ketuk satu kali jendela untuk menampilkan tombol layar penuh, tombol tutup, tombol setelan, dan tindakan kustom yang disediakan oleh aplikasi Anda (misalnya, kontrol putar).

  • Ketuk dua kali jendela untuk beralih antara ukuran PiP saat ini dan ukuran PiP maksimum atau minimum—misalnya, mengetuk dua kali jendela yang dimaksimalkan akan meminimalkannya, dan sebaliknya.

  • Tempelkan jendela dengan menariknya ke tepi kiri atau kanan. Untuk membuka jendela, ketuk bagian yang terlihat di jendela yang disembunyikan, atau tarik keluar.

  • Ubah ukuran jendela PiP menggunakan cubit untuk zoom.

Aplikasi mengontrol kapan aktivitas yang aktif memasuki mode PiP. Berikut beberapa contohnya:

  • Suatu aktivitas dapat memasuki mode PiP saat pengguna mengetuk tombol layar utama atau menggeser ke layar utama. Ini adalah cara Google Maps terus menampilkan rute saat pengguna menjalankan aktivitas lain secara bersamaan.

  • Aplikasi dapat memindahkan video ke mode PiP saat pengguna kembali dari video untuk menjelajahi konten lain.

  • Aplikasi dapat mengalihkan video ke mode PiP saat pengguna menonton bagian akhir sebuah episode konten. Layar utama menampilkan informasi promosi atau ringkasan tentang episode berikutnya dalam serial.

  • Aplikasi dapat memberi pengguna cara mengantrekan konten tambahan saat menonton video. Video terus diputar dalam mode PiP saat layar utama menampilkan aktivitas pemilihan konten.

Mendeklarasikan dukungan PiP

Secara default, sistem tidak otomatis mendukung PiP untuk aplikasi. Jika menginginkan dukungan PiP di aplikasi, daftarkan aktivitas video dalam manifes dengan menyetel android:supportsPictureInPicture ke true. Selain itu, tentukan bahwa aktivitas Anda menangani perubahan konfigurasi tata letak sehingga aktivitas tidak diluncurkan kembali saat perubahan tata letak terjadi selama transisi mode PiP.

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

Alihkan aktivitas ke PiP

Mulai Android 12, Anda dapat mengalihkan aktivitas ke mode PiP dengan menyetel tanda setAutoEnterEnabled ke true. Dengan setelan ini, aktivitas akan otomatis beralih ke mode PiP sesuai kebutuhan tanpa harus memanggil enterPictureInPictureMode() secara eksplisit di onUserLeaveHint. Dan hal ini memiliki manfaat tambahan yaitu menyediakan transisi yang jauh lebih lancar. Untuk mengetahui detailnya, lihat Membuat transisi ke mode PiP lebih lancar dari navigasi gestur.

Jika Anda menargetkan Android 11 atau yang lebih rendah, suatu aktivitas harus memanggil enterPictureInPictureMode() untuk beralih ke mode PiP. Misalnya, kode berikut mengalihkan aktivitas ke mode PiP saat pengguna mengklik tombol khusus di UI aplikasi:

Kotlin

override fun onActionClicked(action: Action) {
    if (action.id.toInt() == R.id.lb_control_picture_in_picture) {
        activity?.enterPictureInPictureMode()
        return
    }
}

Java

@Override
public void onActionClicked(Action action) {
    if (action.getId() == R.id.lb_control_picture_in_picture) {
        getActivity().enterPictureInPictureMode();
        return;
    }
    ...
}

Anda mungkin ingin menyertakan logika yang mengalihkan aktivitas ke mode PiP, bukan ke latar belakang. Misalnya, Google Maps beralih ke mode PiP jika pengguna menekan tombol layar utama atau terbaru saat aplikasi dibuka. Anda dapat mengatasi kasus ini dengan mengganti onUserLeaveHint():

Kotlin

override fun onUserLeaveHint() {
    if (iWantToBeInPipModeNow()) {
        enterPictureInPictureMode()
    }
}

Java

@Override
public void onUserLeaveHint () {
    if (iWantToBeInPipModeNow()) {
        enterPictureInPictureMode();
    }
}

Direkomendasikan: berikan pengalaman transisi PiP yang canggih kepada pengguna

Android 12 menambahkan peningkatan tampilan yang signifikan pada transisi animasi antara jendela layar penuh dan PiP. Sebaiknya terapkan semua perubahan yang berlaku. Setelah melakukannya, perubahan ini akan otomatis diskalakan ke layar besar seperti perangkat foldable dan tablet tanpa perlu tindakan lebih lanjut.

Jika aplikasi Anda tidak menyertakan update yang berlaku, transisi PiP masih berfungsi, tetapi animasinya kurang rapi. Misalnya, transisi dari layar penuh ke mode PiP dapat menyebabkan jendela PiP menghilang selama transisi sebelum muncul kembali saat transisi selesai.

Perubahan ini mencakup hal-hal berikut.

  • Membuat transisi ke mode PiP lebih lancar dari navigasi gestur
  • Menyetel sourceRectHint yang tepat untuk masuk dan keluar dari mode PiP
  • Menonaktifkan pengubahan ukuran yang lancar untuk konten non-video

Lihat contoh PictureInPicture Kotlin Android sebagai referensi untuk memungkinkan pengalaman transisi yang disempurnakan.

Membuat transisi ke mode PiP lebih lancar dari navigasi gestur

Mulai Android 12, tanda setAutoEnterEnabled menyediakan animasi yang jauh lebih lancar untuk bertransisi ke konten video dalam mode PiP menggunakan navigasi gestur—misalnya, saat menggeser ke atas dari layar penuh dari layar penuh.

Selesaikan langkah-langkah berikut untuk membuat perubahan ini dan lihat contoh ini sebagai referensi:

  1. Gunakan setAutoEnterEnabled untuk membuat PictureInPictureParams.Builder:

    Kotlin

    setPictureInPictureParams(PictureInPictureParams.Builder()
        .setAspectRatio(aspectRatio)
        .setSourceRectHint(sourceRectHint)
        .setAutoEnterEnabled(true)
        .build())
    

    Java

    setPictureInPictureParams(new PictureInPictureParams.Builder()
        .setAspectRatio(aspectRatio)
        .setSourceRectHint(sourceRectHint)
        .setAutoEnterEnabled(true)
        .build());
    
  2. Panggil setPictureInPictureParams menggunakan informasi PictureInPictureParams terbaru lebih awal. Aplikasi tidak menunggu callback onUserLeaveHint (seperti yang akan dilakukan di Android 11).

    Misalnya, Anda dapat memanggil setPictureInPictureParams pada pemutaran pertama dan pemutaran berikutnya jika rasio aspek diubah.

  3. Panggil setAutoEnterEnabled(false), tetapi hanya jika diperlukan. Misalnya, Anda mungkin tidak ingin memasukkan PiP jika pemutaran saat ini dalam status dijeda.

Setel sourceRectHint yang tepat untuk masuk dan keluar dari mode PiP

Dimulai dengan pengenalan PiP di Android 8.0, setSourceRectHint menunjukkan area aktivitas yang terlihat setelah transisi ke picture-in-picture—misalnya, batas tampilan video di pemutar video.

Dengan Android 12, sistem menggunakan sourceRectHint untuk menerapkan animasi yang jauh lebih lancar baik saat masuk maupun keluar dari mode PiP.

Untuk menyetel sourceRectHint dengan benar untuk masuk dan keluar dari mode PiP:

  1. Buat PictureInPictureParams menggunakan batas yang tepat sebagai sourceRectHint. Sebaiknya tambahkan juga pemroses perubahan tata letak ke pemutar video:

    Kotlin

    val mOnLayoutChangeListener =
    OnLayoutChangeListener { v: View?, oldLeft: Int,
            oldTop: Int, oldRight: Int, oldBottom: Int, newLeft: Int, newTop:
            Int, newRight: Int, newBottom: Int ->
        val sourceRectHint = Rect()
        mYourVideoView.getGlobalVisibleRect(sourceRectHint)
        val builder = PictureInPictureParams.Builder()
            .setSourceRectHint(sourceRectHint)
        setPictureInPictureParams(builder.build())
    }
    
    mYourVideoView.addOnLayoutChangeListener(mOnLayoutChangeListener)
    

    Java

    private final View.OnLayoutChangeListener mOnLayoutChangeListener =
            (v, oldLeft, oldTop, oldRight, oldBottom, newLeft, newTop, newRight,
            newBottom) -> {
        final Rect sourceRectHint = new Rect();
        mYourVideoView.getGlobalVisibleRect(sourceRectHint);
        final PictureInPictureParams.Builder builder =
            new PictureInPictureParams.Builder()
                .setSourceRectHint(sourceRectHint);
        setPictureInPictureParams(builder.build());
    };
    
    mYourVideoView.addOnLayoutChangeListener(mOnLayoutChangeListener);
    
  2. Jika perlu, update sourceRectHint sebelum sistem memulai transisi keluar. Saat sistem akan keluar dari mode PiP, hierarki tampilan aktivitas diatur ke konfigurasi tujuannya (misalnya, layar penuh). Aplikasi dapat melampirkan pemroses perubahan tata letak ke tampilan root atau tampilan targetnya (seperti tampilan pemutar video) untuk mendeteksi peristiwa dan memperbarui sourceRectHint sebelum animasi dimulai.

    Kotlin

    // Listener is called immediately after the user exits PiP but before animating.
    playerView.addOnLayoutChangeListener { _, left, top, right, bottom,
                        oldLeft, oldTop, oldRight, oldBottom ->
        if (left != oldLeft
            || right != oldRight
            || top != oldTop
            || bottom != oldBottom) {
            // The playerView's bounds changed, update the source hint rect to
            // reflect its new bounds.
            val sourceRectHint = Rect()
            playerView.getGlobalVisibleRect(sourceRectHint)
            setPictureInPictureParams(
                PictureInPictureParams.Builder()
                    .setSourceRectHint(sourceRectHint)
                    .build()
            )
        }
    }
    
    

    Java

    // Listener is called right after the user exits PiP but before
    // animating.
    playerView.addOnLayoutChangeListener((v, left, top, right, bottom,
                        oldLeft, oldTop, oldRight, oldBottom) -> {
        if (left != oldLeft
            || right != oldRight
            || top != oldTop
            || bottom != oldBottom) {
            // The playerView's bounds changed, update the source hint rect to
            // reflect its new bounds.
            final Rect sourceRectHint = new Rect();
            playerView.getGlobalVisibleRect(sourceRectHint);
            setPictureInPictureParams(
                new PictureInPictureParams.Builder()
                    .setSourceRectHint(sourceRectHint)
                    .build());
        }
    });
    
    

Menonaktifkan pengubahan ukuran konten non-video yang lancar

Android 12 menambahkan flag setSeamlessResizeEnabled, yang menyediakan animasi cross-fading yang jauh lebih lancar saat mengubah ukuran konten non-video di jendela PiP. Sebelumnya, mengubah ukuran konten non-video di jendela PiP dapat membuat artefak visual yang mengejutkan.

Untuk menonaktifkan pengubahan ukuran konten non-video yang lancar:

Kotlin

setPictureInPictureParams(PictureInPictureParams.Builder()
    .setSeamlessResizeEnabled(false)
    .build())

Java

setPictureInPictureParams(new PictureInPictureParams.Builder()
    .setSeamlessResizeEnabled(false)
    .build());

Menangani UI selama PiP

Saat aktivitas memasuki atau keluar dari mode PiP, sistem akan memanggil Activity.onPictureInPictureModeChanged() atau Fragment.onPictureInPictureModeChanged().

Anda harus mengganti callback ini untuk menggambar ulang elemen UI aktivitas. Perlu diingat bahwa dalam mode PiP, aktivitas ditampilkan di jendela kecil. Pengguna tidak dapat berinteraksi dengan elemen UI aplikasi Anda saat dalam mode PiP dan detail elemen UI kecil mungkin sulit dilihat. Aktivitas pemutaran video dengan UI yang minimal akan memberikan pengalaman pengguna terbaik.

Jika aplikasi Anda perlu menyediakan tindakan kustom untuk PiP, lihat Menambahkan kontrol di halaman ini. Hapus elemen UI lainnya sebelum aktivitas memasuki PiP dan pulihkan saat aktivitas kembali menjadi layar penuh:

Kotlin

override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean,
                                           newConfig: Configuration) {
    if (isInPictureInPictureMode) {
        // Hide the full-screen UI (controls, etc.) while in PiP mode.
    } else {
        // Restore the full-screen UI.
    }
}

Java

@Override
public void onPictureInPictureModeChanged (boolean isInPictureInPictureMode, Configuration newConfig) {
    if (isInPictureInPictureMode) {
        // Hide the full-screen UI (controls, etc.) while in PiP mode.
    } else {
        // Restore the full-screen UI.
        ...
    }
}

Menambahkan kontrol

Jendela PiP dapat menampilkan kontrol saat pengguna membuka menu jendela tersebut (dengan mengetuk jendela di perangkat seluler, atau memilih menu dari remote TV.)

Jika aplikasi memiliki sesi media aktif, kontrol putar, jeda, berikutnya, dan sebelumnya akan muncul.

Anda juga dapat menentukan tindakan kustom secara eksplisit dengan mem-build PictureInPictureParams dengan PictureInPictureParams.Builder.setActions() sebelum memasuki mode PiP, dan meneruskan parameter saat memasuki mode PiP menggunakan enterPictureInPictureMode(android.app.PictureInPictureParams) atau setPictureInPictureParams(android.app.PictureInPictureParams). Hati-hati. Jika mencoba menambahkan selain getMaxNumPictureInPictureActions(), Anda hanya akan mendapatkan angka maksimumnya.

Melanjutkan pemutaran video saat dalam PiP

Saat aktivitas beralih ke PiP, sistem akan menempatkan aktivitas tersebut dalam status dijeda dan memanggil metode onPause() aktivitas. Pemutaran video tidak akan dijeda dan video akan terus diputar jika aktivitas tersebut dijeda saat dalam mode PiP.

Di Android 7.0 dan yang lebih baru, Anda harus menjeda dan melanjutkan pemutaran video saat sistem memanggil onStop() dan onStart() aktivitas. Dengan begitu, Anda tidak perlu memeriksa apakah aplikasi dalam mode PiP di onPause() dan dapat melanjutkan pemutaran secara eksplisit.

Jika Anda belum menyetel flag setAutoEnterEnabled ke true dan Anda perlu menjeda pemutaran dalam implementasi onPause(), periksa mode PiP dengan memanggil isInPictureInPictureMode() dan menangani pemutaran dengan benar. Contoh:

Kotlin

override fun onPause() {
    super.onPause()
    // If called while in PiP mode, do not pause playback
    if (isInPictureInPictureMode) {
        // Continue playback
    } else {
        // Use existing playback logic for paused Activity behavior.
    }
}

Java

@Override
public void onPause() {
    // If called while in PiP mode, do not pause playback
    if (isInPictureInPictureMode()) {
        // Continue playback
        ...
    } else {
        // Use existing playback logic for paused Activity behavior.
        ...
    }
}

Saat aktivitas keluar dari mode PiP kembali ke mode layar penuh, sistem akan melanjutkan aktivitas dan memanggil metode onResume().

Menggunakan satu aktivitas pemutaran untuk PiP

Di aplikasi, pengguna dapat memilih video baru saat menjelajahi konten di layar utama, selagi aktivitas pemutaran video berlangsung dalam mode PiP. Putar video baru dalam aktivitas pemutaran yang ada dalam mode layar penuh, bukan meluncurkan aktivitas baru yang dapat membingungkan pengguna.

Untuk memastikan satu aktivitas digunakan untuk permintaan pemutaran video dan dialihkan ke atau keluar dari mode PiP sesuai kebutuhan, setel android:launchMode aktivitas ke singleTask dalam manifes:

<activity android:name="VideoActivity"
    ...
    android:supportsPictureInPicture="true"
    android:launchMode="singleTask"
    ...

Dalam aktivitas, ganti onNewIntent() dan tangani video baru, sehingga pemutaran video yang ada akan dihentikan, jika diperlukan.

Praktik terbaik

PiP mungkin dinonaktifkan di perangkat yang memiliki RAM rendah. Sebelum aplikasi menggunakan PiP, pastikan ketersediaannya dengan memanggil hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE).

PiP ditujukan untuk aktivitas yang memutar video dalam mode layar penuh. Saat mengalihkan aktivitas ke mode PiP, jangan tampilkan apa pun kecuali konten video. Lacak kapan aktivitas memasuki mode PiP dan sembunyikan elemen UI, seperti yang dijelaskan dalam Menangani UI selama PiP.

Secara default, aktivitas tidak mendapatkan fokus input saat dalam mode PiP. Untuk menerima peristiwa input saat dalam mode PiP, gunakan MediaSession.setCallback(). Untuk informasi selengkapnya tentang penggunaan setCallback(), lihat Menampilkan kartu Now Playing.

Saat aplikasi dalam mode PiP, pemutaran video di jendela PiP dapat menyebabkan gangguan audio dengan aplikasi lain, seperti aplikasi pemutar musik atau aplikasi penelusuran suara. Untuk menghindarinya, minta fokus audio saat Anda mulai memutar video, dan tangani notifikasi perubahan fokus audio, seperti yang dijelaskan dalam Mengelola Fokus Audio. Jika Anda menerima notifikasi tentang hilangnya fokus audio saat dalam mode PiP, jeda atau hentikan pemutaran video.

Saat aplikasi akan memasuki PiP, perhatikan hanya aktivitas teratas yang memasuki picture-in-picture. Dalam situasi tertentu seperti pada perangkat multi-aplikasi, aktivitas di bawah kini mungkin akan ditampilkan dan menjadi kembali terlihat bersama aktivitas PiP. Anda harus menangani kasus ini sebagaimana mestinya, termasuk aktivitas di bawah untuk mendapatkan callback onResume() atau onPause(). Pengguna juga dapat berinteraksi dengan aktivitas tersebut. Misalnya, jika Anda memiliki aktivitas daftar video yang ditampilkan dan aktivitas yang memutar video dalam mode PiP, pengguna mungkin memilih video baru dari daftar dan aktivitas PiP akan diperbarui sebagaimana mestinya.

Kode contoh tambahan

Untuk mendownload aplikasi contoh yang ditulis di Android, lihat Contoh Picture-in-Picture . Untuk mendownload aplikasi contoh yang ditulis di Kotlin, lihat Contoh PictureInPicture Android (Kotlin).