Tạo nhà cung cấp nội dung nghe nhìn trên đám mây cho Android

Nhà cung cấp nội dung nghe nhìn trên đám mây cung cấp thêm nội dung nghe nhìn trên đám mây cho công cụ chọn ảnh trên Android. Người dùng có thể chọn ảnh hoặc video do nhà cung cấp nội dung nghe nhìn trên đám mây cung cấp khi một ứng dụng sử dụng ACTION_PICK_IMAGES hoặc ACTION_GET_CONTENT để yêu cầu người dùng cung cấp tệp nội dung nghe nhìn. Nhà cung cấp nội dung nghe nhìn trên đám mây cũng có thể cung cấp thông tin về các album. Bạn có thể duyệt xem các album này trong công cụ chọn ảnh của Android.

Trước khi bắt đầu

Hãy cân nhắc những điều sau đây trước khi bắt đầu xây dựng nhà cung cấp nội dung nghe nhìn trên đám mây.

Điều kiện sử dụng

Android đang chạy một chương trình thí điểm để cho phép các ứng dụng do OEM chỉ định trở thành nhà cung cấp nội dung đa phương tiện trên đám mây. Hiện tại, chỉ những ứng dụng do các nhà sản xuất thiết bị gốc đề cử mới đủ điều kiện tham gia chương trình này để trở thành nhà cung cấp nội dung nghe nhìn trên đám mây cho Android. Mỗi OEM có thể đề cử tối đa 3 ứng dụng. Sau khi được phê duyệt, những ứng dụng này sẽ có thể truy cập được dưới dạng nhà cung cấp nội dung nghe nhìn trên đám mây trên mọi thiết bị chạy Android GMS mà chúng được cài đặt.

Android duy trì danh sách phía máy chủ gồm tất cả nhà cung cấp dịch vụ đám mây đủ điều kiện. Mỗi OEM có thể chọn một nhà cung cấp dịch vụ đám mây mặc định bằng cách sử dụng lớp phủ có thể định cấu hình. Các ứng dụng được đề cử phải đáp ứng tất cả các yêu cầu kỹ thuật và vượt qua tất cả các bài kiểm tra chất lượng. Để tìm hiểu thêm về quy trình và các yêu cầu của chương trình thí điểm nhà cung cấp nội dung nghe nhìn trên đám mây của OEM, hãy hoàn tất biểu mẫu yêu cầu.

Quyết định xem bạn có cần tạo một trình cung cấp nội dung nghe nhìn trên đám mây hay không

Nhà cung cấp nội dung nghe nhìn trên đám mây là những ứng dụng hoặc dịch vụ đóng vai trò là nguồn chính để người dùng sao lưu và truy xuất ảnh cũng như video từ đám mây. Nếu ứng dụng của bạn có một thư viện nội dung hữu ích nhưng thường không được dùng làm giải pháp lưu trữ ảnh, thì bạn nên cân nhắc việc tạo một trình cung cấp tài liệu.

Mỗi hồ sơ có một nhà cung cấp dịch vụ đám mây đang hoạt động

Tại một thời điểm, mỗi hồ sơ Android chỉ có thể có tối đa một nhà cung cấp nội dung nghe nhìn trên đám mây đang hoạt động. Người dùng có thể xoá hoặc thay đổi ứng dụng nhà cung cấp nội dung nghe nhìn trên đám mây đã chọn bất cứ lúc nào trong phần cài đặt của công cụ chọn ảnh.

Theo mặc định, công cụ chọn ảnh trên Android sẽ cố gắng tự động chọn một nhà cung cấp dịch vụ đám mây.

  • Nếu chỉ có một nhà cung cấp dịch vụ đám mây đủ điều kiện trên thiết bị, thì ứng dụng đó sẽ tự động được chọn làm nhà cung cấp hiện tại.
  • Nếu có nhiều nhà cung cấp dịch vụ đám mây đủ điều kiện trên thiết bị và một trong số đó khớp với mặc định do OEM chọn, thì ứng dụng do OEM chọn sẽ được chọn.

  • Nếu có nhiều nhà cung cấp dịch vụ đám mây đủ điều kiện trên thiết bị và không có nhà cung cấp nào trùng khớp với chế độ mặc định mà OEM đã chọn, thì sẽ không có ứng dụng nào được chọn.

Tạo trình cung cấp nội dung nghe nhìn trên đám mây

Sơ đồ sau minh hoạ chuỗi sự kiện cả trước và trong phiên chọn ảnh giữa ứng dụng Android, công cụ chọn ảnh Android, MediaProvider của thiết bị cục bộ và CloudMediaProvider.

Sơ đồ trình tự cho thấy quy trình từ công cụ chọn ảnh đến một trình cung cấp nội dung nghe nhìn trên đám mây
Hình 1: Sơ đồ chuỗi sự kiện trong một phiên chọn ảnh.
  1. Hệ thống khởi chạy nhà cung cấp dịch vụ đám mây mà người dùng ưu tiên và định kỳ đồng bộ hoá siêu dữ liệu nội dung nghe nhìn vào phần phụ trợ của công cụ chọn ảnh Android.
  2. Khi một ứng dụng Android chạy công cụ chọn ảnh, trước khi cho người dùng thấy một lưới mục cục bộ hoặc đám mây đã hợp nhất, công cụ chọn ảnh sẽ thực hiện một quy trình đồng bộ hoá gia tăng nhạy cảm với độ trễ với nhà cung cấp dịch vụ đám mây để đảm bảo kết quả luôn mới nhất có thể. Sau khi nhận được phản hồi hoặc khi hết thời hạn, lưới của công cụ chọn ảnh sẽ hiển thị tất cả ảnh có thể truy cập, kết hợp những ảnh được lưu trữ cục bộ trên thiết bị của bạn với những ảnh được đồng bộ hoá từ đám mây.
  3. Trong khi người dùng cuộn, công cụ chọn ảnh sẽ tìm nạp hình thu nhỏ của nội dung nghe nhìn từ nhà cung cấp nội dung nghe nhìn trên đám mây để hiển thị trong giao diện người dùng.
  4. Khi người dùng hoàn tất phiên và kết quả bao gồm một mục nội dung nghe nhìn trên đám mây, công cụ chọn ảnh sẽ yêu cầu các chỉ số mô tả tệp cho nội dung, tạo một URI và cấp quyền truy cập vào tệp cho ứng dụng gọi.
  5. Giờ đây, ứng dụng có thể mở URI và chỉ có quyền đọc nội dung đa phương tiện. Theo mặc định, siêu dữ liệu nhạy cảm sẽ bị loại bỏ thông tin. Công cụ chọn ảnh tận dụng hệ thống tệp FUSE để điều phối hoạt động trao đổi dữ liệu giữa ứng dụng Android và nhà cung cấp nội dung nghe nhìn trên đám mây.

Các vấn đề thường gặp

Sau đây là một số điểm quan trọng cần lưu ý khi cân nhắc việc triển khai:

Tránh các tệp trùng lặp

Vì công cụ chọn ảnh trên Android không có cách nào kiểm tra trạng thái nội dung nghe nhìn trên đám mây, nên CloudMediaProvider cần cung cấp MEDIA_STORE_URI trong hàng con trỏ của mọi tệp có cả trên đám mây và trên thiết bị cục bộ, nếu không người dùng sẽ thấy các tệp trùng lặp trong công cụ chọn ảnh.

Tối ưu hoá kích thước hình ảnh để hiển thị bản xem trước

Điều rất quan trọng là tệp được trả về từ onOpenPreview không phải là hình ảnh có độ phân giải đầy đủ và tuân thủ Size đang được yêu cầu. Hình ảnh quá lớn sẽ làm tăng thời gian tải trong giao diện người dùng, còn hình ảnh quá nhỏ có thể bị vỡ hình hoặc mờ tuỳ theo kích thước màn hình của thiết bị.

Xử lý hướng chính xác

Nếu hình thu nhỏ được trả về trong onOpenPreview không chứa dữ liệu EXIF, thì chúng phải được trả về theo đúng hướng để tránh trường hợp hình thu nhỏ bị xoay sai trong lưới xem trước.

Ngăn chặn hành vi truy cập trái phép

Kiểm tra MANAGE_CLOUD_MEDIA_PROVIDERS_PERMISSION trước khi trả về dữ liệu cho phương thức gọi từ ContentProvider. Việc này sẽ ngăn các ứng dụng trái phép truy cập vào dữ liệu trên đám mây.

Lớp CloudMediaProvider

Bắt nguồn từ android.content.ContentProvider, lớp CloudMediaProvider bao gồm các phương thức như trong ví dụ sau:

Kotlin

abstract class CloudMediaProvider : ContentProvider() {

    @NonNull
    abstract override fun onGetMediaCollectionInfo(@NonNull bundle: Bundle): Bundle

    @NonNull
    override fun onQueryAlbums(@NonNull bundle: Bundle): Cursor = TODO("Implement onQueryAlbums")

    @NonNull
    abstract override fun onQueryDeletedMedia(@NonNull bundle: Bundle): Cursor

    @NonNull
    abstract override fun onQueryMedia(@NonNull bundle: Bundle): Cursor

    @NonNull
    abstract override fun onOpenMedia(
        @NonNull string: String,
        @Nullable bundle: Bundle?,
        @Nullable cancellationSignal: CancellationSignal?
    ): ParcelFileDescriptor

    @NonNull
    abstract override fun onOpenPreview(
        @NonNull string: String,
        @NonNull point: Point,
        @Nullable bundle: Bundle?,
        @Nullable cancellationSignal: CancellationSignal?
    ): AssetFileDescriptor

    @Nullable
    override fun onCreateCloudMediaSurfaceController(
        @NonNull bundle: Bundle,
        @NonNull callback: CloudMediaSurfaceStateChangedCallback
    ): CloudMediaSurfaceController? = null
}

Java

public abstract class CloudMediaProvider extends android.content.ContentProvider {

  @NonNull
  public abstract android.os.Bundle onGetMediaCollectionInfo(@NonNull android.os.Bundle);

  @NonNull
  public android.database.Cursor onQueryAlbums(@NonNull android.os.Bundle);

  @NonNull
  public abstract android.database.Cursor onQueryDeletedMedia(@NonNull android.os.Bundle);

  @NonNull
  public abstract android.database.Cursor onQueryMedia(@NonNull android.os.Bundle);

  @NonNull
  public abstract android.os.ParcelFileDescriptor onOpenMedia(@NonNull String, @Nullable android.os.Bundle, @Nullable android.os.CancellationSignal) throws java.io.FileNotFoundException;

  @NonNull
  public abstract android.content.res.AssetFileDescriptor onOpenPreview(@NonNull String, @NonNull android.graphics.Point, @Nullable android.os.Bundle, @Nullable android.os.CancellationSignal) throws java.io.FileNotFoundException;

  @Nullable
  public android.provider.CloudMediaProvider.CloudMediaSurfaceController onCreateCloudMediaSurfaceController(@NonNull android.os.Bundle, @NonNull android.provider.CloudMediaProvider.CloudMediaSurfaceStateChangedCallback);
}

Lớp CloudMediaProviderContract

Ngoài lớp triển khai CloudMediaProvider chính, công cụ chọn ảnh của Android còn kết hợp một lớp CloudMediaProviderContract. Lớp này trình bày khả năng tương tác giữa công cụ chọn ảnh và trình cung cấp nội dung nghe nhìn trên đám mây, bao gồm các khía cạnh như MediaCollectionInfo cho các thao tác đồng bộ hoá, các cột Cursor dự kiến và các phần bổ sung Bundle.

Kotlin

object CloudMediaProviderContract {

    const val EXTRA_ALBUM_ID = "android.provider.extra.ALBUM_ID"
    const val EXTRA_LOOPING_PLAYBACK_ENABLED = "android.provider.extra.LOOPING_PLAYBACK_ENABLED"
    const val EXTRA_MEDIA_COLLECTION_ID = "android.provider.extra.MEDIA_COLLECTION_ID"
    const val EXTRA_PAGE_SIZE = "android.provider.extra.PAGE_SIZE"
    const val EXTRA_PAGE_TOKEN = "android.provider.extra.PAGE_TOKEN"
    const val EXTRA_PREVIEW_THUMBNAIL = "android.provider.extra.PREVIEW_THUMBNAIL"
    const val EXTRA_SURFACE_CONTROLLER_AUDIO_MUTE_ENABLED = "android.provider.extra.SURFACE_CONTROLLER_AUDIO_MUTE_ENABLED"
    const val EXTRA_SYNC_GENERATION = "android.provider.extra.SYNC_GENERATION"
    const val MANAGE_CLOUD_MEDIA_PROVIDERS_PERMISSION = "com.android.providers.media.permission.MANAGE_CLOUD_MEDIA_PROVIDERS"
    const val PROVIDER_INTERFACE = "android.content.action.CLOUD_MEDIA_PROVIDER"

    object MediaColumns {
        const val DATE_TAKEN_MILLIS = "date_taken_millis"
        const val DURATION_MILLIS = "duration_millis"
        const val HEIGHT = "height"
        const val ID = "id"
        const val IS_FAVORITE = "is_favorite"
        const val MEDIA_STORE_URI = "media_store_uri"
        const val MIME_TYPE = "mime_type"
        const val ORIENTATION = "orientation"
        const val SIZE_BYTES = "size_bytes"
        const val STANDARD_MIME_TYPE_EXTENSION = "standard_mime_type_extension"
        const val STANDARD_MIME_TYPE_EXTENSION_ANIMATED_WEBP = 3 // 0x3
        const val STANDARD_MIME_TYPE_EXTENSION_GIF = 1 // 0x1
        const val STANDARD_MIME_TYPE_EXTENSION_MOTION_PHOTO = 2 // 0x2
        const val STANDARD_MIME_TYPE_EXTENSION_NONE = 0 // 0x0
        const val SYNC_GENERATION = "sync_generation"
        const val WIDTH = "width"
    }

    object AlbumColumns {
        const val DATE_TAKEN_MILLIS = "date_taken_millis"
        const val DISPLAY_NAME = "display_name"
        const val ID = "id"
        const val MEDIA_COUNT = "album_media_count"
        const val MEDIA_COVER_ID = "album_media_cover_id"
    }

    object MediaCollectionInfo {
        const val ACCOUNT_CONFIGURATION_INTENT = "account_configuration_intent"
        const val ACCOUNT_NAME = "account_name"
        const val LAST_MEDIA_SYNC_GENERATION = "last_media_sync_generation"
        const val MEDIA_COLLECTION_ID = "media_collection_id"
    }
}

Java

public final class CloudMediaProviderContract {

  public static final String EXTRA_ALBUM_ID = "android.provider.extra.ALBUM_ID";
  public static final String EXTRA_LOOPING_PLAYBACK_ENABLED = "android.provider.extra.LOOPING_PLAYBACK_ENABLED";
  public static final String EXTRA_MEDIA_COLLECTION_ID = "android.provider.extra.MEDIA_COLLECTION_ID";
  public static final String EXTRA_PAGE_SIZE = "android.provider.extra.PAGE_SIZE";
  public static final String EXTRA_PAGE_TOKEN = "android.provider.extra.PAGE_TOKEN";
  public static final String EXTRA_PREVIEW_THUMBNAIL = "android.provider.extra.PREVIEW_THUMBNAIL";
  public static final String EXTRA_SURFACE_CONTROLLER_AUDIO_MUTE_ENABLED = "android.provider.extra.SURFACE_CONTROLLER_AUDIO_MUTE_ENABLED";
  public static final String EXTRA_SYNC_GENERATION = "android.provider.extra.SYNC_GENERATION";
  public static final String MANAGE_CLOUD_MEDIA_PROVIDERS_PERMISSION = "com.android.providers.media.permission.MANAGE_CLOUD_MEDIA_PROVIDERS";
  public static final String PROVIDER_INTERFACE = "android.content.action.CLOUD_MEDIA_PROVIDER";
}

// Columns available for every media item
public static final class CloudMediaProviderContract.MediaColumns {

  public static final String DATE_TAKEN_MILLIS = "date_taken_millis";
  public static final String DURATION_MILLIS = "duration_millis";
  public static final String HEIGHT = "height";
  public static final String ID = "id";
  public static final String IS_FAVORITE = "is_favorite";
  public static final String MEDIA_STORE_URI = "media_store_uri";
  public static final String MIME_TYPE = "mime_type";
  public static final String ORIENTATION = "orientation";
  public static final String SIZE_BYTES = "size_bytes";
  public static final String STANDARD_MIME_TYPE_EXTENSION = "standard_mime_type_extension";
  public static final int STANDARD_MIME_TYPE_EXTENSION_ANIMATED_WEBP = 3; // 0x3
  public static final int STANDARD_MIME_TYPE_EXTENSION_GIF = 1; // 0x1 
  public static final int STANDARD_MIME_TYPE_EXTENSION_MOTION_PHOTO = 2; // 0x2 
  public static final int STANDARD_MIME_TYPE_EXTENSION_NONE = 0; // 0x0 
  public static final String SYNC_GENERATION = "sync_generation";
  public static final String WIDTH = "width";
}

// Columns available for every album item
public static final class CloudMediaProviderContract.AlbumColumns {

  public static final String DATE_TAKEN_MILLIS = "date_taken_millis";
  public static final String DISPLAY_NAME = "display_name";
  public static final String ID = "id";
  public static final String MEDIA_COUNT = "album_media_count";
  public static final String MEDIA_COVER_ID = "album_media_cover_id";
}

// Media Collection metadata that is cached by the OS to compare sync states.
public static final class CloudMediaProviderContract.MediaCollectionInfo {

  public static final String ACCOUNT_CONFIGURATION_INTENT = "account_configuration_intent";
  public static final String ACCOUNT_NAME = "account_name";
  public static final String LAST_MEDIA_SYNC_GENERATION = "last_media_sync_generation";
  public static final String MEDIA_COLLECTION_ID = "media_collection_id";
}

onGetMediaCollectionInfo

Hệ điều hành sử dụng phương thức onGetMediaCollectionInfo() để đánh giá tính hợp lệ của các mục nội dung nghe nhìn trên đám mây được lưu vào bộ nhớ đệm và xác định hoạt động đồng bộ hoá cần thiết với nhà cung cấp nội dung nghe nhìn trên đám mây. Do hệ điều hành có thể gọi thường xuyên, onGetMediaCollectionInfo() được coi là quan trọng đối với hiệu suất; bạn cần tránh các thao tác kéo dài hoặc tác dụng phụ có thể ảnh hưởng tiêu cực đến hiệu suất. Hệ điều hành lưu vào bộ nhớ đệm các phản hồi trước đó từ phương thức này và so sánh chúng với các phản hồi tiếp theo để xác định các hành động thích hợp.

Kotlin

abstract fun onGetMediaCollectionInfo(extras: Bundle): Bundle

Java

@NonNull
public abstract Bundle onGetMediaCollectionInfo(@NonNull Bundle extras);

Gói MediaCollectionInfo được trả về bao gồm các hằng số sau:

onQueryMedia

Phương thức onQueryMedia() được dùng để điền vào lưới ảnh chính trong công cụ chọn ảnh ở nhiều chế độ xem. Các lệnh gọi này có thể nhạy cảm với độ trễ và có thể được gọi trong quá trình đồng bộ hoá chủ động trong nền hoặc trong các phiên công cụ chọn ảnh khi cần có trạng thái đồng bộ hoá đầy đủ hoặc gia tăng. Giao diện người dùng của công cụ chọn ảnh sẽ không chờ phản hồi vô thời hạn để hiển thị kết quả và có thể hết thời gian chờ đối với những yêu cầu này cho mục đích giao diện người dùng. Con trỏ được trả về vẫn sẽ cố gắng được xử lý vào cơ sở dữ liệu của bộ chọn ảnh cho các phiên sau này.

Phương thức này trả về một Cursor đại diện cho tất cả các mục nội dung nghe nhìn trong bộ sưu tập nội dung nghe nhìn (có thể được lọc theo các phần bổ sung được cung cấp và sắp xếp theo thứ tự thời gian đảo ngược của MediaColumns#DATE_TAKEN_MILLIS (các mục gần đây nhất sẽ xuất hiện trước).

Gói CloudMediaProviderContract được trả về bao gồm các hằng số sau:

Nhà cung cấp nội dung nghe nhìn trên đám mây phải đặt CloudMediaProviderContract#EXTRA_MEDIA_COLLECTION_ID trong Bundle được trả về. Nếu không đặt giá trị này, bạn sẽ gặp lỗi và Cursor được trả về sẽ không hợp lệ. Nếu nhà cung cấp nội dung nghe nhìn trên đám mây xử lý mọi bộ lọc trong các phần bổ sung được cung cấp, thì nhà cung cấp đó phải thêm khoá vào ContentResolver#EXTRA_HONORED_ARGS trong phần Cursor#setExtras được trả về.

onQueryDeletedMedia

Phương thức onQueryDeletedMedia() được dùng để đảm bảo các mục đã xoá trong tài khoản đám mây được xoá đúng cách khỏi giao diện người dùng của công cụ chọn ảnh. Do độ nhạy tiềm ẩn về độ trễ, những lệnh gọi này có thể được bắt đầu trong quá trình:

  • Đồng bộ hoá chủ động ở chế độ nền
  • Phiên công cụ chọn ảnh (khi cần có trạng thái đồng bộ hoá đầy đủ hoặc gia tăng)

Giao diện người dùng của bộ chọn ảnh ưu tiên trải nghiệm người dùng phản hồi nhanh và sẽ không chờ phản hồi vô thời hạn. Để duy trì các hoạt động tương tác mượt mà, có thể xảy ra tình trạng hết thời gian chờ. Mọi Cursor được trả về vẫn sẽ được xử lý vào cơ sở dữ liệu của bộ chọn ảnh cho các phiên sau này.

Phương thức này trả về một Cursor đại diện cho tất cả các mục nội dung nghe nhìn đã bị xoá trong toàn bộ bộ sưu tập nội dung nghe nhìn trong phiên bản nhà cung cấp hiện tại do onGetMediaCollectionInfo() trả về. Bạn có thể lọc các mục này theo thông tin bổ sung (không bắt buộc). Nhà cung cấp nội dung nghe nhìn trên đám mây phải đặt CloudMediaProviderContract#EXTRA_MEDIA_COLLECTION_ID trong phần Cursor#setExtras được trả về. Nếu không đặt, đây là một lỗi và sẽ làm mất hiệu lực Cursor. Nếu nhà cung cấp xử lý bất kỳ bộ lọc nào trong các tiện ích bổ sung được cung cấp, thì nhà cung cấp đó phải thêm khoá vào ContentResolver#EXTRA_HONORED_ARGS.

onQueryAlbums

Phương thức onQueryAlbums() dùng để tìm nạp danh sách các đĩa nhạc trên đám mây có trong nhà cung cấp dịch vụ đám mây và siêu dữ liệu liên quan. Hãy xem CloudMediaProviderContract.AlbumColumns để biết thêm thông tin chi tiết.

Phương thức này trả về một Cursor đại diện cho tất cả các mục trong album trong bộ sưu tập nội dung nghe nhìn, có thể được lọc theo các phần bổ sung được cung cấp và sắp xếp theo thứ tự thời gian đảo ngược của AlbumColumns#DATE_TAKEN_MILLIS, các mục gần đây nhất sẽ xuất hiện trước. Nhà cung cấp nội dung nghe nhìn trên đám mây phải đặt CloudMediaProviderContract#EXTRA_MEDIA_COLLECTION_ID trong Cursor được trả về. Nếu không đặt giá trị này, bạn sẽ gặp lỗi và Cursor được trả về sẽ không hợp lệ. Nếu nhà cung cấp xử lý bất kỳ bộ lọc nào trong các thành phần bổ sung được cung cấp, thì nhà cung cấp đó phải thêm khoá vào ContentResolver#EXTRA_HONORED_ARGS trong Cursor được trả về.

onOpenMedia

Phương thức onOpenMedia() sẽ trả về nội dung nghe nhìn có kích thước đầy đủ do mediaId đã cung cấp xác định. Nếu phương thức này chặn trong khi tải nội dung xuống thiết bị, bạn nên định kỳ kiểm tra CancellationSignal đã cung cấp để huỷ các yêu cầu bị bỏ qua.

onOpenPreview

Phương thức onOpenPreview() sẽ trả về hình thu nhỏ của size được cung cấp cho mục có mediaId được cung cấp. Hình thu nhỏ phải ở CloudMediaProviderContract.MediaColumns#MIME_TYPE ban đầu và dự kiến sẽ có độ phân giải thấp hơn nhiều so với mục do onOpenMedia trả về. Nếu phương thức này bị chặn trong khi tải nội dung xuống thiết bị, bạn nên định kỳ kiểm tra CancellationSignal đã cung cấp để huỷ các yêu cầu bị bỏ dở.

onCreateCloudMediaSurfaceController

Phương thức onCreateCloudMediaSurfaceController() sẽ trả về một CloudMediaSurfaceController dùng để kết xuất bản xem trước của các mục nội dung nghe nhìn hoặc null nếu không được hỗ trợ kết xuất bản xem trước.

CloudMediaSurfaceController quản lý việc kết xuất bản xem trước của các mục nội dung nghe nhìn trên các thực thể nhất định của Surface. Các phương thức của lớp này được thiết kế để hoạt động không đồng bộ và không được chặn bằng cách thực hiện bất kỳ thao tác nào có mức sử dụng cao. Một thực thể CloudMediaSurfaceController duy nhất chịu trách nhiệm kết xuất nhiều mục nội dung nghe nhìn được liên kết với nhiều nền tảng.

CloudMediaSurfaceController hỗ trợ danh sách sau đây gồm các lệnh gọi lại theo vòng đời: