+4

Picture-in-picture support - Android

I. Overview

Picture-in-picture (PIP) mode là một dạng của multi-window mode, chủ yếu được dùng cho playback video. Nó cho phép user xem tiếp video trong 1 cửa sổ nhỏ ghim ở góc màn hình (mặc định là góc trên) trong khi duyệt các nội dung khác bên trong hoặc bên ngoài ứng dụng. Android 8.0 (API 26) trở lên có thể sử dụng chế độ này. Một vài trường hợp app chuyển sang chế độ PIP:

  • Khi user tap home hoặc recents button để chọn ứng dụng khác.
  • Khi user back về trong lúc đang xem video để duyệt nội dung khác.
  • Khi user đã xem tới cuối một video trong một playlist. Video chuyển sang chế độ PIP, màn hình chính hiển thị quảng cáo hoặc nội dung tóm tắt của video tiếp theo.
  • Khi user lựa chọn các nội dung để tạo thành một playlist trong lúc đang xem video.

II. Implementation

1. Declaring picture-in-picture support

Mặc định hệ thống sẽ không hỗ trợ chế độ PIP cho activity của bạn. Để có thể khởi chạy ở chế độ này, bạn cần register nó bên trong manifest android:supportsPictureInPicture="true". Thêm nữa, bạn cần chỉ định activity có thể xử lý configuration changes vì activity không được khởi chạy lại khi chuyển sang chế độ PIP.

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

2. Switching your activity to picture-in-picture

Để khởi chạy chế độ PIP, activity cần call method enterPictureInPictureMode(). Ví dụ, khi user click vào một button trên UI:

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

Trong một số trường hợp, bạn có thể muốn activity chuyển sang chế độ PIP thay vì bị đưa xuống background. Ví dụ, khi user tap home hoặc recents button. Bạn có thể catch case này bằng cách override callback: onUserLeaveHint():

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

3. Handling UI during picture-in-picture

Khi activity enters hoặc exits PIP mode, system calls Activity.onPictureInPictureModeChanged() hoặc Fragment.onPictureInPictureModeChanged(). Bạn cần override lại những callback này để vẽ lại UI. Nhớ rằng khi chuyển sang PIP mode, video playback sẽ hiên thị dưới một cửa sổ nhỏ và chỉ cần hiện thị thêm video controls. Vì vậy, cần remove những thành phần khác để có thể hiển thị một cách tối giản nhất UI của video playback khi chuyển sang PIP mode và vẽ lại chúng khi thoát khỏi PIP mode hòng mang đến trải nghiệm tốt nhất cho user.

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

3.1. Adding controls

Chế độ PIP có thể hiển thị controls khi user mở window's menu (bằng cách tapping vào cửa sổ trên điện thoại hoặc chọn menu trên TV remote). Nếu ứng dụng đang có một media session hoạt động. Các button play, pause, next và previous sẽ xuất hiện. Bạn cũng có thể custom những action cụ thể bởi building PictureInPictureParams với PictureInPictureParams.Builder.setActions() trước khi mở PIP mode và truyền params khi mở PIP mode bằng enterPictureInPictureMode(android.app.PictureInPictureParams) hoặc setPictureInPictureParams(android.app.PictureInPictureParams). Bạn chỉ có thể add số lượng actions tối đa bằng với số lượng của phương thức getMaxNumPictureInPictureActions().

4. Continuing video playback while picture-in-picture

Khi activity chuyển sang chế độ PIP, hệ thống đưa activity về trạng thái pause và gọi callback onPause(). Trong chế độ PIP, video playback không nên bị pause và nên được tiếp tục kể cả khi activity ở trạng thái pause. Từ Android 7.0 trở lên, bạn có thể pause và resume video playback khi activity gọi tới onStop()onStart(). Làm như vậy bạn sẽ không cần kiểm tra liệu app có ở PIP mode tại onPause() mà vẫn có thể tiếp tục playback video. Nếu bạn muốn implement logic code tại onPause() khi app enter PIP mode, bạn có thể kiểm tra PIP mode bằng cách calling isInPictureInPictureMode().

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

Khi app thoát khỏi chế độ PIP và quay trở lại chế độ full-screen, activity calls method onResume().

5. Using a single playback activity for picture-in-picture

User có thể select một video khác trong khi đang có 1 video chạy ở chế độ PIP. Hãy play video mới bằng playback activity hiện tại ở chế độ full-screen, thay vì khởi chạy một activity mới, điều đó sẽ gây hoang mang cho user. Để đảm bảo chỉ có 1 playback activity được chạy khi thoát khỏi chế độ PIP, bạn sử dụng android:launchMode là singleTask trong manifest.

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

Bên trong activity, bạn cần override lại method onNewIntent() để stop playback video hiện có nếu cần và xử lý video mới.

III. Best Practices

  • Device của bạn cần đảm bảo còn đủ RAM khả dụng để khởi chạy PIP mode. Để đảm bảo, hãy kiểm tra trước khi khởi chạy bằng cách gọi hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE).
  • Khi có một video playback hiển thị ở chế độ PIP, bạn nên cân nhắc tránh hiện thị những thông tin quan trọng ở main screen tại những vùng bị video playback che khuất.
  • Khi một activity ở chế độ PIP, mặc định nó sẽ không thể get input focus. Để nhận input events khi ở chế độ PIP, bạn sử dụng MediaSession.setCallback(). For more information: Displaying a Now Playing Card.
  • Nếu bạn playback video ở chế độ PIP trong khi sử dụng một ứng dụng khác, việc nhiễu âm thanh giữa 2 ứng dụng là hoàn toàn có thể xảy ra. Để tránh điều này, bạn cần request audio focus khi start playing video và xử lý audio focus change notifications, more information: Managing Audio Focus. Nếu bạn mất focus audio khi ở chế độ PIP, hãy pause hoặc stop video playback.

IV. Demo

Giờ chúng ta hãy cùng bắt tay vào làm một demo nho nhỏ nhé.

Kịch bản:

  • MainScreen hiển thị 2 item video thumbnail.
  • User tapping vào item đầu tiên > video đầu tiên chạy trên màn hình ở chế độ fullscreen, góc trên bên phải hiển thị icon pip mode.
  • User tapping vào icon pip mode > video enter pip mode thành công.

Tiến hành:

  • Mình có chuẩn bị 2 video ngắn đưa vào thư mục res/raw và 2 hình ảnh thumbnail tương ứng đặt trong res/drawable.

  • Tiếp theo mình khai báo PLaybackActivity trong manifest với các option cần thiết.

image.png

  • Sau đó mình set click event cho các item để start playback acitivity với param tương ứng.

  • Trong PlaybackActivity, mình sử dụng VideoView để play video, func prepareSource() để khởi tạo và start video playback.

  • Khi user tapping vào icon pip mode, mình gọi func enterPipMode() để enter PIP mode.

  • Trong layout của mình, chỉ có 1 views thuộc dạng unnecessary khi app enter PIP mode là icon pip mode. Mình xử lý ẩn hiện view này bằng callback onPictureInPictureModeChanged(). Ngoài ra, button exit PIP mode và back to fullscreen mode thì hệ thống đã cung cấp sẵn cho mình UI kèm theo các chức năng tương ứng nên mình không cần tác động gì thêm.

  • Để xử lý case có 1 video playback đang ở chế độ PIP, user tapping vào 1 item video khác trên main screen thì mình thực hiện override phương thức onNewIntent() và xử lý logic tương ứng.

  • Và cuối cùng là thành quả của mình: Hình 1: List thumbnail video lại main screen.

Hình 2: Video playback.

Hình 3: Video on PIP mode.

Hình 4: Video on PIP mode when user tapping on window.

Rất đơn giản phải không nào. Các bạn có thể tham khảo toàn bộ source code tại đây nếu cần thiết. Ngoài ra, các bạn nên mày mò setup thêm các optional khác để hiểu rõ hơn về PIP mode nhé. Chúc các bạn học tốt.

Tham khảo: PIP Android

Cảm ơn các bạn đã đọc, hẹn gặp lại ở những bài viết sau!


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí