Chiếu nội dung nghe nhìn

API android.media.projection được giới thiệu trong Android 5 (API cấp 21) cho phép bạn chụp lại nội dung của màn hình thiết bị dưới dạng luồng nội dung nghe nhìn mà bạn có thể phát lại, quay hoặc truyền sang các thiết bị khác như TV.

Android 14 (API cấp 34) ra mắt tính năng chia sẻ màn hình ứng dụng, cho phép người dùng chia sẻ một cửa sổ ứng dụng thay vì toàn bộ màn hình thiết bị, bất kể chế độ cửa sổ. Tính năng chia sẻ màn hình ứng dụng sẽ loại trừ thanh trạng thái, thanh điều hướng, thông báo và các thành phần giao diện người dùng hệ thống khác khỏi màn hình được chia sẻ, ngay cả khi tính năng chia sẻ màn hình ứng dụng được dùng để chụp một ứng dụng ở chế độ toàn màn hình. Chỉ nội dung của ứng dụng đã chọn mới được chia sẻ.

Tính năng chia sẻ màn hình ứng dụng đảm bảo quyền riêng tư của người dùng, tăng năng suất và cải thiện khả năng làm nhiều việc cùng lúc bằng cách cho phép người dùng chạy nhiều ứng dụng nhưng chỉ cho phép chia sẻ nội dung với một ứng dụng duy nhất.

Ba cách thể hiện màn hình

Tính năng chiếu nội dung đa phương tiện sẽ chụp lại nội dung của màn hình thiết bị hoặc cửa sổ ứng dụng, sau đó chiếu hình ảnh được chụp lên màn hình ảo để kết xuất hình ảnh đó trên Surface.

Màn hình thiết bị thực tế chiếu lên màn hình ảo. Nội dung của màn hình ảo được ghi vào `Bề mặt` (Surface) do ứng dụng cung cấp.
Hình 1. Màn hình thiết bị thực tế hoặc cửa sổ ứng dụng được chiếu lên màn hình ảo. Màn hình ảo được ghi vào Surface do ứng dụng cung cấp.

Ứng dụng này cung cấp Surface thông qua MediaRecorder, SurfaceTexture hoặc ImageReader. Các thành phần này sẽ sử dụng nội dung của màn hình được chụp và cho phép bạn quản lý hình ảnh được kết xuất trên Surface theo thời gian thực. Bạn có thể lưu hình ảnh dưới dạng bản ghi hoặc truyền vào TV hoặc thiết bị khác.

Màn hình thực

Bắt đầu phiên chiếu nội dung đa phương tiện bằng cách lấy mã thông báo giúp ứng dụng của bạn có thể chụp lại nội dung của màn hình thiết bị hoặc cửa sổ ứng dụng. Mã thông báo này được biểu thị bằng một thực thể của lớp MediaProjection.

Sử dụng phương thức getMediaProjection() của dịch vụ hệ thống MediaProjectionManager để tạo một thực thể MediaProjection khi bạn bắt đầu một hoạt động mới. Bắt đầu hoạt động bằng một ý định từ phương thức createScreenCaptureIntent() để chỉ định thao tác chụp màn hình:

Kotlin

val mediaProjectionManager = getSystemService(MediaProjectionManager::class.java)
var mediaProjection : MediaProjection

val startMediaProjection = registerForActivityResult(
    StartActivityForResult()
) { result ->
    if (result.resultCode == RESULT_OK) {
        mediaProjection = mediaProjectionManager
            .getMediaProjection(result.resultCode, result.data!!)
    }
}

startMediaProjection.launch(mediaProjectionManager.createScreenCaptureIntent())

Java

final MediaProjectionManager mediaProjectionManager =
    getSystemService(MediaProjectionManager.class);
final MediaProjection[] mediaProjection = new MediaProjection[1];

ActivityResultLauncher<Intent> startMediaProjection = registerForActivityResult(
    new StartActivityForResult(),
    result -> {
        if (result.getResultCode() == Activity.RESULT_OK) {
            mediaProjection[0] = mediaProjectionManager
                .getMediaProjection(result.getResultCode(), result.getData());
        }
    }
);

startMediaProjection.launch(mediaProjectionManager.createScreenCaptureIntent());

Màn hình ảo

Tâm điểm của tính năng chiếu nội dung đa phương tiện là màn hình ảo mà bạn tạo bằng cách gọi createVirtualDisplay() cho thực thể MediaProjection:

Kotlin

virtualDisplay = mediaProjection.createVirtualDisplay(
                     "ScreenCapture",
                     width,
                     height,
                     screenDensity,
                     DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
                     surface,
                     null, null)

Java

virtualDisplay = mediaProjection.createVirtualDisplay(
                     "ScreenCapture",
                     width,
                     height,
                     screenDensity,
                     DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
                     surface,
                     null, null);

Các tham số widthheight chỉ định kích thước của màn hình ảo. Để lấy các giá trị cho chiều rộng và chiều cao, hãy sử dụng các API WindowMetrics được giới thiệu trong Android 11 (API cấp 30). (Để biết thông tin chi tiết, hãy xem phần Kích thước chiếu nội dung nghe nhìn.)

Bề mặt

Định kích thước bề mặt chiếu nội dung đa phương tiện để có đầu ra ở độ phân giải thích hợp. Đặt kích thước lớn (độ phân giải thấp) để truyền sang TV hoặc màn hình máy tính và kích thước nhỏ (độ phân giải cao) để quay màn hình thiết bị.

Kể từ Android 12L (API cấp 32), khi kết xuất nội dung đã chụp trên bề mặt, hệ thống sẽ điều chỉnh tỷ lệ nội dung một cách đồng nhất, duy trì tỷ lệ khung hình, sao cho cả hai kích thước của nội dung (chiều rộng và chiều cao) bằng hoặc nhỏ hơn kích thước tương ứng của bề mặt. Sau đó, nội dung đã chụp sẽ được căn giữa trên nền tảng.

Phương pháp điều chỉnh tỷ lệ của Android 12L cải thiện tính năng truyền màn hình sang TV và các màn hình lớn khác bằng cách tối đa hoá kích thước của hình ảnh bề mặt trong khi vẫn đảm bảo tỷ lệ khung hình phù hợp.

Quyền sử dụng dịch vụ trên nền trước

Nếu ứng dụng của bạn nhắm đến Android 14 trở lên, thì tệp kê khai ứng dụng phải bao gồm nội dung khai báo quyền cho loại dịch vụ trên nền trước mediaProjection:

<manifest ...>
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
    <application ...>
        <service
            android:name=".MyMediaProjectionService"
            android:foregroundServiceType="mediaProjection"
            android:exported="false">
        </service>
    </application>
</manifest>

Bắt đầu dịch vụ chiếu nội dung đa phương tiện bằng lệnh gọi đến startForeground().

Nếu bạn không chỉ định loại dịch vụ trên nền trước trong lệnh gọi, thì loại này sẽ mặc định là số nguyên theo bit của các loại dịch vụ trên nền trước được xác định trong tệp kê khai. Nếu tệp kê khai không chỉ định bất kỳ loại dịch vụ nào, hệ thống sẽ gửi MissingForegroundServiceTypeException.

Ứng dụng của bạn phải yêu cầu người dùng đồng ý trước mỗi phiên chiếu nội dung đa phương tiện. Phiên là một lệnh gọi duy nhất đến createVirtualDisplay(). Bạn chỉ được sử dụng mã thông báo MediaProjection một lần để thực hiện lệnh gọi.

Trên Android 14 trở lên, phương thức createVirtualDisplay() sẽ gửi một SecurityException nếu ứng dụng của bạn thực hiện một trong những thao tác sau:

  • Truyền một thực thể Intent được trả về từ createScreenCaptureIntent() đến getMediaProjection() nhiều lần
  • Gọi createVirtualDisplay() nhiều lần trên cùng một thực thể MediaProjection

Kích thước chiếu nội dung nghe nhìn

Phép chiếu nội dung đa phương tiện có thể chụp toàn bộ màn hình thiết bị hoặc cửa sổ ứng dụng bất kể chế độ cửa sổ.

Kích thước ban đầu

Với tính năng chiếu nội dung đa phương tiện toàn màn hình, ứng dụng của bạn phải xác định kích thước màn hình thiết bị. Trong tính năng chia sẻ màn hình ứng dụng, ứng dụng của bạn sẽ không thể xác định kích thước của màn hình đã chụp cho đến khi người dùng chọn vùng chụp. Do đó, kích thước ban đầu của mọi phép chiếu nội dung nghe nhìn là kích thước màn hình thiết bị.

Sử dụng phương thức WindowManager getMaximumWindowMetrics() của nền tảng để trả về đối tượng WindowMetrics cho màn hình thiết bị ngay cả khi ứng dụng lưu trữ nội dung chiếu đa phương tiện đang ở chế độ nhiều cửa sổ, chỉ chiếm một phần màn hình.

Để tương thích xuống đến API cấp 14, hãy sử dụng phương thức WindowMetricsCalculator computeMaximumWindowMetrics() từ thư viện WindowManager của Jetpack.

Gọi phương thức WindowMetrics getBounds() để lấy chiều rộng và chiều cao của màn hình thiết bị.

Thay đổi kích thước

Kích thước của hình chiếu nội dung nghe nhìn có thể thay đổi khi thiết bị được xoay hoặc người dùng chọn một cửa sổ ứng dụng làm vùng chụp trong tính năng chia sẻ màn hình ứng dụng. Hình chiếu nội dung nghe nhìn có thể được tạo dạng hòm thư nếu nội dung được chụp có kích thước khác với các chỉ số cửa sổ tối đa thu được khi thiết lập hình chiếu nội dung nghe nhìn.

Để đảm bảo phép chiếu nội dung đa phương tiện khớp chính xác với kích thước của nội dung đã chụp cho bất kỳ vùng nào đã chụp và trên các chế độ xoay thiết bị, hãy sử dụng lệnh gọi lại onCapturedContentResize() để đổi kích thước nội dung đã chụp. (Để biết thêm thông tin, hãy xem phần Tuỳ chỉnh ở phần sau).

Tuỳ chỉnh

Ứng dụng của bạn có thể tuỳ chỉnh trải nghiệm người dùng khi chiếu nội dung đa phương tiện bằng các API MediaProjection.Callback sau:

  • onCapturedContentVisibilityChanged(): Cho phép ứng dụng lưu trữ (ứng dụng đã bắt đầu tính năng chiếu nội dung đa phương tiện) hiển thị hoặc ẩn nội dung được chia sẻ.

    Sử dụng lệnh gọi lại này để tuỳ chỉnh giao diện người dùng của ứng dụng dựa trên việc người dùng có nhìn thấy vùng đã chụp hay không. Ví dụ: nếu người dùng nhìn thấy ứng dụng của bạn và đang hiển thị nội dung đã chụp trong giao diện người dùng của ứng dụng, đồng thời người dùng cũng nhìn thấy ứng dụng đã chụp (như được chỉ báo thông qua lệnh gọi lại này), thì người dùng sẽ thấy cùng một nội dung hai lần. Sử dụng lệnh gọi lại để cập nhật giao diện người dùng của ứng dụng nhằm ẩn nội dung đã chụp và giải phóng không gian bố cục trong ứng dụng cho nội dung khác.

  • onCapturedContentResize(): Cho phép ứng dụng lưu trữ thay đổi kích thước của hình chiếu nội dung đa phương tiện trên màn hình ảo và hình chiếu nội dung đa phương tiện Surface dựa trên kích thước của vùng hiển thị được chụp.

    Được kích hoạt bất cứ khi nào nội dung được chụp (một cửa sổ ứng dụng hoặc màn hình thiết bị đầy đủ) thay đổi kích thước (do xoay thiết bị hoặc ứng dụng được chụp chuyển sang chế độ cửa sổ khác). Sử dụng API này để đổi kích thước cả màn hình ảo và bề mặt để đảm bảo tỷ lệ khung hình khớp với nội dung đã chụp và nội dung chụp không có khung viền hòm thư.

Khôi phục tài nguyên

Ứng dụng của bạn nên đăng ký lệnh gọi lại MediaProjection onStop() để được thông báo khi phiên chiếu nội dung đa phương tiện bị dừng và trở nên không hợp lệ. Khi phiên bị dừng, ứng dụng của bạn sẽ giải phóng các tài nguyên mà ứng dụng đó lưu giữ, chẳng hạn như màn hình ảo và bề mặt chiếu. Phiên chiếu nội dung đa phương tiện đã dừng không thể tạo màn hình ảo mới nữa, ngay cả khi ứng dụng của bạn chưa tạo màn hình ảo cho phiên chiếu nội dung đa phương tiện đó.

Hệ thống sẽ gọi lệnh gọi lại khi bản chiếu nội dung nghe nhìn kết thúc. Việc chấm dứt này có thể xảy ra vì một số lý do, chẳng hạn như:

  • người dùng dừng phiên bằng giao diện người dùng của ứng dụng hoặc khối thanh trạng thái chiếu nội dung nghe nhìn của hệ thống
  • màn hình đang được khoá
  • một phiên chiếu nội dung đa phương tiện khác bắt đầu
  • quá trình của ứng dụng bị tắt

Nếu ứng dụng của bạn không đăng ký lệnh gọi lại, thì mọi lệnh gọi đến createVirtualDisplay() sẽ gửi IllegalStateException.

Ngừng

Android 14 trở lên bật tính năng chia sẻ màn hình ứng dụng theo mặc định. Mỗi phiên chiếu nội dung đa phương tiện cho phép người dùng chia sẻ cửa sổ ứng dụng hoặc toàn bộ màn hình.

Ứng dụng của bạn có thể chọn không chia sẻ màn hình ứng dụng bằng cách gọi phương thức createScreenCaptureIntent(MediaProjectionConfig) với đối số MediaProjectionConfig được trả về từ lệnh gọi đến createConfigForDefaultDisplay().

Lệnh gọi đến createScreenCaptureIntent(MediaProjectionConfig) với đối số MediaProjectionConfig được trả về từ lệnh gọi đến createConfigForUserChoice() giống với hành vi mặc định, tức là lệnh gọi đến createScreenCaptureIntent().

Ứng dụng có thể thay đổi kích thước

Luôn đảm bảo ứng dụng chiếu nội dung đa phương tiện có thể đổi kích thước (resizeableActivity="true"). Các ứng dụng có thể đổi kích thước hỗ trợ thay đổi cấu hình thiết bị và chế độ nhiều cửa sổ (xem phần Hỗ trợ nhiều cửa sổ).

Nếu ứng dụng không thể thay đổi kích thước thì nó phải truy vấn các ranh giới hiển thị của cửa sổ và sử dụng getMaximumWindowMetrics() để truy xuất WindowMetrics của khu vực hiển thị tối đa có sẵn cho ứng dụng :

Kotlin

val windowContext = context.createWindowContext(context.display!!,
      WindowManager.LayoutParams.TYPE_APPLICATION, null)
val projectionMetrics = windowContext.getSystemService(WindowManager::class.java)
      .maximumWindowMetrics

Java

Context windowContext = context.createWindowContext(context.getDisplay(),
      WindowManager.LayoutParams.TYPE_APPLICATION, null);
WindowMetrics projectionMetrics = windowContext.getSystemService(WindowManager.class)
      .getMaximumWindowMetrics();

Khối thanh trạng thái và tính năng tự động dừng

Các lỗ hổng liên quan đến tính năng chiếu màn hình làm lộ dữ liệu riêng tư của người dùng, chẳng hạn như thông tin tài chính, vì người dùng không nhận ra màn hình thiết bị của họ đang được chia sẻ.

Android 15 (API cấp 35) QPR1 giới thiệu một khối thanh trạng thái mới có kích thước lớn và nổi bật, giúp người dùng cảnh báo về mọi hoạt động chiếu màn hình đang diễn ra. Người dùng có thể nhấn vào khối này để ngừng chia sẻ, truyền hoặc ghi lại màn hình của họ.

Trên Android 15 QPR1 trở lên, tính năng chiếu màn hình sẽ tự động dừng khi màn hình thiết bị bị khoá.

Hình 2. Khối trên thanh trạng thái để chia sẻ màn hình, truyền màn hình và ghi màn hình.

Tài nguyên khác

Để biết thêm thông tin về phép chiếu nội dung đa phương tiện, vui lòng xem nội dung Quay video và phát lại âm thanh.