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.

Tính năng Hình trong hình cũng được hỗ trợ trên các thiết bị chạy hệ điều hành Android TV tương thích Android 14 (API cấp 34) trở lên. Mặc dù có nhiều điểm tương đồng, nhưng có các yếu tố khác cần cân nhắc khi sử dụng PiP trên TV.

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 tối đa hoặc kích thước PiP tối thiểu (ví dụ: nhấn đúp vào một cửa sổ phóng to) giảm thiểu nó 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ổ vào cạnh trái hoặc phải. Để hủy lưu trữ hãy nhấn vào phần hiển thị của cửa sổ đã lưu trữ hoặc kéo phần đó 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 trở về nhà. Đây là cách Google Maps tiếp tục hiển thị chỉ đường 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ế độ Hình trong hình khi người dùng quay lại từ video để duyệt qua 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 danh sách chờ trong khi họ xem video. Video sẽ tiếp tục phát ở chế độ PiP trong khi màn hì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 (Hình trong hình)

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 Hình trong hình trong ứng dụng, hãy đăng ký hoạt động video của bạn 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 sẽ 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ó thay đổi về bố cục 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 của bạn sang chế độ Hình trong hình

Kể từ Android 12, bạn có thể chuyển hoạt động sang chế độ Hình trong hình bằng cách cài đặt cờ setAutoEnterEnabled thành true. Với chế độ cài đặt này, hoạt động tự động chuyển sang chế độ PiP khi cần mà không cần gọi một cách rõ ràng enterPictureInPictureMode() trong onUserLeaveHint. Và công cụ này có giúp quá trình chuyển đổi diễn ra suôn sẻ hơn nhiều. Để biết chi tiết, hãy xem phần Tạo giúp chuyển đổi sang chế độ PiP mượt mà hơn khi 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 chuyển một hoạt động thành Chế độ PiP 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ế độ Hình trong hình hoạt động ở 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 chỉn chu

Android 12 bổ sung những điểm cải tiến đáng kể về mặt thẩm mỹ cho các hiệu ứng chuyển đổi động giữa cửa sổ toàn màn hình và cửa sổ PiP. Chúng tôi thực sự khuyên bạn nên triển khai tất cả các thay đổi có thể áp dụng; khi bạn đã thực hiện xong, những thay đổi này sẽ tự động mở rộng màn hình lớn như thiết bị có thể gập lại và máy tính bảng mà 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 hiện hành, thì hiệu ứng chuyển đổi PiP vẫn sẽ diễn ra hoạt động tốt, nhưng ảnh động ít được trau chuốt hơn. Ví dụ: chuyển đổi từ chế độ toàn màn hình sang chế độ PiP có thể khiến cửa sổ PiP biến mất trong hiệu ứng chuyển đổi trước khi nó xuất hiện lại khi quá trình chuyển đổi hoàn tất.

Những thay đổi này bao gồm những nội dung sau.

  • Việc chuyển đổi sang chế độ PiP mượt mà hơn khi thao tác bằng cử chỉ
  • Đặt một sourceRectHint thích 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 Android Mẫu hình trong hình ảnh Kotlin làm tài liệu tham khảo để mang lại 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 cung cấp nhiều ảnh động mượt mà hơn để chuyển sang nội dung video ở chế độ PiP bằng cử chỉ điều hướng, chẳng hạn như khi vuốt lên từ chế độ toàn màn hình để chuyển đến màn hình chí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 để biết tham chiếu:

  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 không đợi 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 trên lần phát đầu tiên và bất kỳ lần phát nào tiếp theo 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 video đang phát ở chế độ tạm dừng trạng thái.

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

Bắt đầu từ việc ra mắt tính năng PiP trong Android 8.0, setSourceRectHint biểu thị khu vực hoạt động có thể nhìn thấy sau khi chuyển đổi sang hình trong hình, ví dụ: giới hạn lượt xem video trong trình phát video.

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

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

  1. Tạo PictureInPictureParams sử dụng các giới hạn thích hợp như sourceRectHint. Bạn cũng nên đính kèm trình nghe thay đổi bố cục cho 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 khởi động 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 bố trí cho cấu hình đích đến (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 chế độ xem mục tiêu (chẳng hạn như chế độ xem trình phát video) để phát hiện sự kiện và hãy 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, cờ này cung cấp ảnh động mờ dần khi đổi kích thước nội dung không phải video trong chế độ Hình trong hình cửa sổ. Trước đây, bạn có thể đổi kích thước nội dung không phải video trong cửa sổ Hình trong hình các hiệu ứng 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 thao tác tuỳ chỉnh cho tính năng Hình trong hình, hãy xem phần Thêm 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ó nội dung nghe nhìn đang hoạt động phiên, sau đó phát, các nút điều khiển 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 hành động tuỳ chỉnh bằng cách tạo PictureInPictureParams thông qua tính năng PictureInPictureParams.Builder.setActions() trước khi vào chế độ PiP và truyền các thông số khi bạn chuyển sang chế độ PiP bằ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 vào vị trí tạm dừng Trạng thái và gọi thuộc tính của hoạt động Phương thức onPause(). Video dài không nên tạm dừng phát mà thay vào đó sẽ tiếp tục phát nếu hoạt động tạm dừng khi chuyển sang chế độ Hình trong hình.

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 hay không trong onPause() và tiếp tục phát một cách rõ ràng.

Nếu chưa đặt cờ setAutoEnterEnabled thành true và bạn cần tạm dừng phát trong quá trình triển khai onPause(), kiểm tra chế độ PiP bằng cách gọi isInPictureInPictureMode() và xử lý hoạt động 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ở về chế độ toàn màn hình, hệ thống sẽ tiếp tục hoạt động của bạn và gọi Phương thức onResume().

Sử dụng một hoạt động phát duy nhất cho chế độ 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 còn 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ì khởi chạy hoạt động mới có thể khiến người dùng nhầm lẫn.

Để đảm bảo sử dụng một hoạt động duy nhất cho các yêu cầu phát video và chuyển đổi vào hoặc tắt chế độ PiP nếu cần, hãy đặt android:launchMode của hoạt động thành singleTask trong tệp kê khai của bạn:

<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 khi hoạt động của bạn sẽ chuyển sang chế độ Hình trong hình và ẩn các thành phần trên giao diện người dùng, như mô tả trong bài viết 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 Hiển thị tính năng 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 ra âm thanh can thiệp vào 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 trường hợp này, hãy yêu cầu quyền phát âm thanh khi bạn bắt đầu phát video và chọn thông báo về việc thay đổi quyền phát âm thanh, như mô tả trong bài viết Quản lý âm thanh Trọng tâm. Nếu bạn nhận được thông báo mất quyền phát âm thanh khi ở chế độ PiP, hãy tạm dừng hoặc ngừng phát video.

Khi ứng dụng của bạn chuẩn bị chuyển sang chế độ PiP, hãy lưu ý rằng chỉ hoạt động trên cùng được nhập 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.

Các đoạn mã mẫu khác

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