Thêm video bằng tính năng hình trong hình (PiP)

Kể từ Android 8.0 (API cấp 26), Android sẽ cho phép chạy các hoạt động ở chế độ Hình trong hình (PiP). PiP là một loại chế độ đặc biệt dành cho nhiều cửa sổ, chủ yếu dùng để phát video. Tính năng này cho phép người dùng xem video trong một cửa sổ nhỏ được ghim vào góc màn hình trong khi di chuyển giữa các ứng dụng hoặc duyệt xem nội dung trên màn hình chính.

PiP tận dụng các API nhiều cửa sổ có trong Android 7.0 để cung cấp cửa sổ lớp phủ của video đã ghim. Để thêm PiP vào ứng dụng, bạn cần đăng ký các hoạt động hỗ trợ PiP, chuyển hoạt động sang Chế độ PiP (nếu cần) và đảm bảo ẩn đi các thành phần giao diện người dùng cũng như tiếp tục phát video khi hoạt động đó được ở chế độ PiP.

Cửa sổ PiP xuất hiện ở lớp trên cùng của màn hình, ở một góc mà hệ thống chọn.

Cách người dùng có thể tương tác với cửa sổ PiP

Người dùng có thể kéo cửa sổ PiP đến một vị trí khác. Kể từ Android 12, người dùng cũng có thể:

  • Nhấn một lần vào cửa sổ để hiện nút bật/tắt chế độ toàn màn hình, nút đóng, nút cài đặt và các hành động tuỳ chỉnh mà ứng dụng của bạn cung cấp (ví dụ: nút điều khiển trình phát).

  • Nhấn đúp vào cửa sổ để chuyển đổi giữa kích thước PiP hiện tại và kích thước PiP tối đa hoặc tối thiểu. Ví dụ: nhấn đúp vào một cửa sổ được phóng to sẽ thu nhỏ kích thước đó và điều ngược lại cũng đúng.

  • Lưu trữ cửa sổ bằng cách kéo cửa sổ sang cạnh trái hoặc phải. Để huỷ lưu trữ cửa sổ, hãy nhấn vào phần hiển thị của cửa sổ đã lưu trữ hoặc kéo cửa sổ đó ra.

  • Đổi kích thước cửa sổ PiP bằng cách chụm để thu phóng.

Ứng dụng của bạn kiểm soát thời điểm hoạt động hiện tại chuyển sang chế độ PiP. Dưới đây là một số ví dụ:

  • Một hoạt động có thể chuyển sang chế độ PiP khi người dùng nhấn vào nút màn hình chính hoặc vuốt lên đến màn hình chính. Đây là cách Google Maps tiếp tục hiển thị thông tin đường đi trong khi người dùng chạy một hoạt động khác cùng lúc.

  • Ứng dụng của bạn có thể chuyển video sang chế độ PiP khi người dùng di chuyển từ video đó trở lại để duyệt xem nội dung khác.

  • Ứng dụng của bạn có thể chuyển video sang chế độ PiP trong khi người dùng xem đến cuối tập nội dung. Màn hình chính cho thấy thông tin quảng bá hoặc tóm tắt về tập tiếp theo trong loạt video.

  • Ứng dụng của bạn có thể giúp người dùng thêm nội dung bổ sung vào hàng đợi trong khi xem video. Video sẽ tiếp tục phát ở chế độ PiP trong khi màn hình chính hiển thị một hoạt động lựa chọn nội dung.

Khai báo tính năng hỗ trợ PiP

Theo mặc định, hệ thống không tự động hỗ trợ tính năng PiP cho ứng dụng. Nếu bạn muốn hỗ trợ tính năng PiP trong ứng dụng, hãy đăng ký hoạt động video trong tệp kê khai bằng cách đặt android:supportsPictureInPicture thành true. Ngoài ra, hãy chỉ định rằng hoạt động của bạn xử lý các thay đổi về cấu hình bố cục để hoạt động của bạn không chạy lại khi các thay đổi về bố cục diễn ra trong quá trình chuyển đổi chế độ PiP.

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

Chuyển hoạt động sang chế độ Hình trong hình

Kể từ Android 12, bạn có thể chuyển đổi hoạt động sang chế độ PiP bằng cách đặt cờ setAutoEnterEnabled thành true. Với chế độ cài đặt này, một hoạt động sẽ tự động chuyển sang chế độ PiP nếu cần mà không cần phải gọi enterPictureInPictureMode() một cách rõ ràng trong onUserLeaveHint. Điều này còn có thêm một lợi ích là cung cấp quá trình chuyển đổi mượt mà hơn nhiều. Để biết thông tin chi tiết, hãy xem phần Thực hiện chuyển đổi sang chế độ PiP mượt mà hơn từ thao tác bằng cử chỉ.

Nếu bạn đang nhắm đến Android 11 trở xuống, thì một hoạt động phải gọi enterPictureInPictureMode() để chuyển sang chế độ PiP. Ví dụ: mã sau đây sẽ chuyển một hoạt động sang chế độ Hình trong hình khi người dùng nhấp vào một nút chuyên dụng trong giao diện người dùng của ứng dụng:

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

Bạn nên thêm logic để chuyển một hoạt động sang chế độ PiP thay vì chuyển vào chế độ nền. Ví dụ: Google Maps sẽ chuyển sang chế độ PiP nếu người dùng nhấn nút màn hình chính hoặc nút gần đây trong khi ứng dụng đang đi theo chỉ dẫn. Bạn có thể xác định trường hợp này bằng cách ghi đè onUserLeaveHint():

Kotlin

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

Java

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

Đề xuất: mang đến cho người dùng trải nghiệm chuyển đổi PiP mượt mà

Android 12 bổ sung những điểm cải tiến đáng kể về mặt thẩm mỹ cho hiệu ứng chuyển đổi ảnh động giữa cửa sổ toàn màn hình và cửa sổ Hình trong hình. Bạn nên triển khai mọi thay đổi hiện hành. Sau khi thực hiện, những thay đổi này sẽ tự động mở rộng sang các màn hình lớn như thiết bị có thể gập lại và máy tính bảng mà bạn không cần phải làm gì thêm.

Nếu ứng dụng của bạn không có các bản cập nhật phù hợp, thì các hiệu ứng chuyển đổi PiP vẫn hoạt động nhưng ảnh động sẽ kém bóng hơn. Ví dụ: việc chuyển đổi từ chế độ toàn màn hình sang chế độ Hình trong hình có thể khiến cửa sổ Hình trong hình biến mất trong quá trình chuyển đổi trước khi xuất hiện lại khi quá trình chuyển đổi hoàn tất.

Những thay đổi này liên quan đến những điều sau.

  • Hiệu quả chuyển đổi sang chế độ PiP mượt mà hơn khi thao tác bằng cử chỉ
  • Đặt một sourceRectHint phù hợp để vào và thoát chế độ Hình trong hình
  • Tắt tính năng đổi kích thước liền mạch cho nội dung không phải video

Tham khảo mẫu MovieInPicture cho Android làm tài liệu tham khảo để tạo ra một trải nghiệm chuyển đổi chỉn chu.

Khiến việc chuyển đổi sang chế độ PiP mượt mà hơn khi thao tác bằng cử chỉ

Kể từ Android 12, cờ setAutoEnterEnabled sẽ cung cấp ảnh động mượt mà hơn nhiều để chuyển đổi sang nội dung video ở chế độ Hình trong hình bằng cách sử dụng cử chỉ thao tác (ví dụ: khi vuốt lên màn hình chính từ chế độ toàn màn hình).

Hãy hoàn tất các bước sau để thực hiện thay đổi này và tham khảo mẫu này:

  1. Sử dụng setAutoEnterEnabled để tạo 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. Gọi setPictureInPictureParams thông báo sớm PictureInPictureParams. Ứng dụng sẽ không chờ lệnh gọi lại onUserLeaveHint (như đã thực hiện trong Android 11).

    Ví dụ: bạn có thể muốn gọi setPictureInPictureParams trong lần phát đầu tiên và mọi lần phát sau đó nếu tỷ lệ khung hình thay đổi.

  3. Gọi setAutoEnterEnabled(false) nhưng chỉ khi cần thiết. Ví dụ: có thể bạn không muốn nhập PiP nếu hoạt động phát hiện tại đang ở trạng thái tạm dừng.

Đặt một sourceRectHint phù hợp để vào và thoát chế độ Hình trong hình

Bắt đầu từ việc giới thiệu PiP trong Android 8.0, setSourceRectHint đã cho biết khu vực hoạt động có thể hiển thị sau khi chuyển sang chế độ hình trong hình (ví dụ: giới hạn chế độ xem video trong trình phát video).

Với Android 12, hệ thống sử dụng sourceRectHint để triển khai ảnh động mượt mà hơn nhiều cả khi vào và thoát khỏi chế độ PiP.

Cách đặt sourceRectHint đúng cách nhằm vào và thoát chế độ Hình trong hình:

  1. Tạo PictureInPictureParams bằng cách sử dụng các ranh giới thích hợp dưới dạng sourceRectHint. Bạn cũng nên đính kèm trình nghe thay đổi bố cục vào trình phát 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. Nếu cần, hãy cập nhật sourceRectHint trước khi hệ thống bắt đầu quá trình chuyển đổi thoát. Khi hệ thống sắp thoát khỏi chế độ PiP, hệ phân cấp khung hiển thị của hoạt động sẽ được bố trí cho cấu hình đích (ví dụ: toàn màn hình). Ứng dụng có thể đính kèm trình nghe thay đổi bố cục vào khung hiển thị gốc hoặc khung hiển thị đích (chẳng hạn như khung hiển thị trình phát video) để phát hiện sự kiện và cập nhật sourceRectHint trước khi ảnh động bắt đầu.

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

Tắt tính năng đổi kích thước liền mạch cho nội dung không phải video

Android 12 thêm cờ setSeamlessResizeEnabled, cung cấp ảnh động mờ dần trên nhiều khung hình khi đổi kích thước nội dung không phải video trong cửa sổ PiP. Trước đây, việc đổi kích thước nội dung không phải video trong cửa sổ PiP có thể tạo ra các cấu phần phần mềm hình ảnh gây khó chịu.

Cách tắt tính năng đổi kích thước liền mạch cho nội dung không phải video:

Kotlin

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

Java

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

Xử lý giao diện người dùng trong chế độ Hình trong hình

Khi hoạt động chuyển sang hoặc thoát khỏi chế độ PiP, hệ thống sẽ gọi Activity.onPictureInPictureModeChanged() hoặc Fragment.onPictureInPictureModeChanged().

Bạn nên ghi đè các lệnh gọi lại này để vẽ lại các thành phần giao diện người dùng của hoạt động. Xin lưu ý rằng ở chế độ PiP, hoạt động của bạn xuất hiện trong một cửa sổ nhỏ. Người dùng không thể tương tác với các thành phần giao diện người dùng của ứng dụng khi đang ở chế độ PiP và có thể khó thấy chi tiết về các thành phần giao diện người dùng nhỏ. Các hoạt động phát video trên giao diện người dùng ở mức tối thiểu sẽ đem lại trải nghiệm người dùng tốt nhất.

Nếu ứng dụng của bạn cần cung cấp các thao tác tuỳ chỉnh cho PiP, hãy xem phần Thêm tuỳ chọn kiểm soát trên trang này. Xoá các thành phần giao diện người dùng khác trước khi hoạt động chuyển sang chế độ PiP và khôi phục các thành phần này khi hoạt động chuyển sang chế độ toàn màn hình:

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

Thêm chế độ điều khiển

Cửa sổ PiP có thể hiện các tuỳ chọn điều khiển khi người dùng mở trình đơn của cửa sổ (bằng cách nhấn vào cửa sổ trên thiết bị di động hoặc chọn menu (trình đơn) trong điều khiển từ xa của TV).

Nếu ứng dụng có một phiên nội dung nghe nhìn đang hoạt động, thì các chế độ điều khiển phát, tạm dừng, tiếp theo và trước đó sẽ xuất hiện.

Bạn cũng có thể chỉ định rõ ràng các thao tác tuỳ chỉnh bằng cách tạo PictureInPictureParams bằng PictureInPictureParams.Builder.setActions() trước khi chuyển sang chế độ PiP, và truyền các tham số khi chuyển sang chế độ PiP bằng cách sử dụng enterPictureInPictureMode(android.app.PictureInPictureParams) hoặc setPictureInPictureParams(android.app.PictureInPictureParams). Hãy thận trọng. Nếu cố thêm nhiều hơn getMaxNumPictureInPictureActions(), bạn sẽ chỉ nhận được số lượng tối đa.

Tiếp tục phát video khi ở chế độ Hình trong hình

Khi hoạt động của bạn chuyển sang chế độ PiP, hệ thống sẽ đặt hoạt động ở trạng thái tạm dừng và gọi phương thức onPause() của hoạt động đó. Bạn không nên tạm dừng phát video mà hãy tiếp tục phát nếu hoạt động đó bị tạm dừng khi ở chế độ PiP.

Với Android 7.0 trở lên, bạn nên tạm dừng và tiếp tục phát video khi hệ thống gọi onStop()onStart() của hoạt động của bạn. Bằng cách này, bạn có thể tránh phải kiểm tra xem ứng dụng của mình có đang ở chế độ PiP không trên onPause () hay không và tiếp tục phát một cách rõ ràng.

Nếu bạn chưa đặt cờ setAutoEnterEnabled thành true và cần tạm dừng phát trong quá trình triển khai onPause(), hãy kiểm tra chế độ PiP bằng cách gọi isInPictureInPictureMode() và xử lý chế độ phát một cách thích hợp. Ví dụ:

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

Khi hoạt động của bạn chuyển từ chế độ PiP trở lại chế độ toàn màn hình, hệ thống sẽ tiếp tục hoạt động và gọi phương thức onResume().

Sử dụng một hoạt động phát duy nhất cho PiP (Hình trong hình)

Trong ứng dụng của bạn, người dùng có thể chọn một video mới khi duyệt tìm nội dung trên màn hình chính, trong khi một hoạt động phát video đang ở chế độ PiP. Phát video mới trong hoạt động phát hiện có ở chế độ toàn màn hình, thay vì chạy một hoạt động mới có thể khiến người dùng nhầm lẫn.

Để đảm bảo một hoạt động được dùng cho các yêu cầu phát video và chuyển vào hoặc ra khỏi chế độ PiP nếu cần, hãy đặt android:launchMode của hoạt động đó thành singleTask trong tệp kê khai:

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

Trong hoạt động của bạn, hãy ghi đè onNewIntent() và xử lý video mới, dừng mọi hoạt động phát video hiện có, nếu cần.

Các phương pháp hay nhất

Bạn có thể tắt tính năng PiP trên các thiết bị có dung lượng RAM thấp. Trước khi ứng dụng của bạn sử dụng tính năng PiP, hãy kiểm tra để đảm bảo rằng tính năng này có sẵn bằng cách gọi hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE).

PiP được sử dụng cho các hoạt động phát video toàn màn hình. Khi chuyển đổi hoạt động sang chế độ PiP, hãy tránh hiện nội dung nào khác ngoài nội dung video. Theo dõi thời điểm hoạt động của bạn chuyển sang chế độ PiP và ẩn các thành phần trên giao diện người dùng, như mô tả trong phần Xử lý giao diện người dùng trong PiP.

Khi ở một chế độ PiP, theo mặc định, hoạt động sẽ không thu thập được quyền phát đầu vào. Để nhận sự kiện đầu vào khi ở chế độ PiP, hãy sử dụng MediaSession.setCallback(). Để biết thêm thông tin về cách sử dụng setCallback(), hãy xem phần Hiển thị thẻ Phát hiện nhạc.

Khi ứng dụng của bạn đang ở chế độ Hình trong hình, việc phát video trong cửa sổ Hình trong hình có thể gây nhiễu âm thanh với một ứng dụng khác, chẳng hạn như ứng dụng trình phát nhạc hoặc ứng dụng tìm kiếm bằng giọng nói. Để tránh tình trạng này, hãy yêu cầu quyền phát âm thanh khi bạn bắt đầu phát video và xử lý các thông báo về việc thay đổi quyền phát âm thanh, như mô tả trong phần Quản lý tập trung vào âm thanh. Nếu bạn nhận được thông báo về việc mất quyền phát âm thanh khi ở chế độ Hình trong hình, hãy tạm dừng hoặc dừng phát video.

Khi ứng dụng của bạn sắp chuyển sang chế độ Hình trong hình, hãy lưu ý rằng chỉ hoạt động trên cùng mới chuyển sang chế độ hình trong hình. Trong một số trường hợp như trên các thiết bị nhiều cửa sổ, có thể hoạt động bên dưới sẽ xuất hiện và hiển thị lại cùng với hoạt động PiP. Bạn nên xử lý trường hợp này một cách phù hợp, bao gồm cả hoạt động dưới đây khi nhận được lệnh gọi lại onResume() hoặc onPause(). Cũng có thể người dùng có thể tương tác với hoạt động. Ví dụ: nếu bạn có một hoạt động danh sách video đang hiển thị và hoạt động phát video trong chế độ PiP, có thể người dùng sẽ chọn video mới trong danh sách và hoạt động PiP phải được cập nhật tương ứng.

Mã mẫu khác

Để tải ứng dụng mẫu viết cho Android, hãy xem phần Mẫu Hình trong hình. Để tải ứng dụng mẫu viết bằng Kotlin, hãy xem nội dung Mẫu PictureInPicture cho Android (Kotlin).