Tích hợp tính năng Truyền vào ứng dụng Android

Hướng dẫn dành cho nhà phát triển này mô tả cách thêm tính năng hỗ trợ Google Cast vào ứng dụng gửi Android bằng SDK Người gửi Android.

Thiết bị di động hoặc máy tính xách tay là người gửi điều khiển chế độ phát và thiết bị Google Cast là Receiver (Trình nhận) hiển thị nội dung trên TV.

Khung người gửi đề cập đến tệp nhị phân của thư viện lớp Truyền và các tài nguyên liên quan có trong thời gian chạy trên người gửi. Ứng dụng người gửi hoặc Ứng dụng truyền là một ứng dụng cũng chạy trên người gửi. Ứng dụng Bộ thu trên web là ứng dụng HTML chạy trên thiết bị hỗ trợ Cast.

Khung người gửi sử dụng thiết kế gọi lại không đồng bộ để thông báo cho ứng dụng người gửi về các sự kiện và chuyển đổi giữa các trạng thái khác nhau của vòng đời ứng dụng Cast.

Luồng ứng dụng

Các bước sau đây mô tả quy trình thực thi cấp cao điển hình cho một ứng dụng Android của người gửi:

  • Khung Truyền sẽ tự động bắt đầu MediaRouter khám phá thiết bị dựa trên vòng đời Activity.
  • Khi người dùng nhấp vào nút Truyền, khung sẽ hiển thị hộp thoại Truyền với danh sách các thiết bị Truyền đã phát hiện.
  • Khi người dùng chọn một thiết bị Truyền, khung sẽ cố gắng chạy ứng dụng Bộ thu web trên thiết bị truyền.
  • Khung này gọi lệnh gọi lại trong ứng dụng người gửi để xác nhận rằng đã khởi chạy ứng dụng Trình nhận web.
  • Khung này tạo một kênh giao tiếp giữa người gửi và ứng dụng Web Receiver.
  • Khung này sử dụng kênh giao tiếp để tải và điều khiển tính năng phát nội dung đa phương tiện trên Bộ thu web.
  • Khung này sẽ đồng bộ hóa trạng thái phát nội dung đa phương tiện giữa người gửi và Bộ thu web: khi người dùng thực hiện hành động trên giao diện người dùng của người gửi, khung sẽ chuyển các yêu cầu điều khiển nội dung nghe nhìn đó đến Bộ thu web và khi Bộ thu web gửi nội dung cập nhật trạng thái phương tiện, khung sẽ cập nhật trạng thái của giao diện người dùng người gửi.
  • Khi người dùng nhấp vào nút Truyền để ngắt kết nối khỏi thiết bị truyền, khung sẽ ngắt kết nối ứng dụng người gửi khỏi Bộ thu web.

Để biết danh sách đầy đủ tất cả các lớp, phương thức và sự kiện trong SDK Android của Google Cast, hãy xem Tài liệu tham khảo API người gửi Google Cast dành cho Android. Các phần sau đây trình bày các bước để bạn thêm tính năng Truyền vào ứng dụng Android.

Định cấu hình tệp kê khai Android

Tệp AndroidManifest.xml của ứng dụng yêu cầu bạn phải định cấu hình các phần tử sau đây cho SDK truyền:

uses-sdk

Đặt các cấp độ API Android tối thiểu và mục tiêu mà SDK Truyền hỗ trợ. Hiện tại, giá trị tối thiểu là API cấp 19 và mục tiêu là API cấp 28.

<uses-sdk
        android:minSdkVersion="19"
        android:targetSdkVersion="28" />

android:theme

Đặt giao diện của ứng dụng dựa trên phiên bản SDK Android tối thiểu. Ví dụ: nếu không triển khai giao diện riêng, bạn nên sử dụng một biến thể của Theme.AppCompat khi nhắm đến một phiên bản SDK Android tối thiểu là Lollipop.

<application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/Theme.AppCompat" >
       ...
</application>

Khởi động ngữ cảnh truyền

Khung này có một đối tượng singleton toàn cục, CastContext, giúp điều phối tất cả các tương tác của khung.

Ứng dụng của bạn phải triển khai giao diện OptionsProvider để cung cấp các tuỳ chọn cần thiết nhằm khởi chạy singleton CastContext. OptionsProvider cung cấp một phiên bản của CastOptions chứa các tùy chọn ảnh hưởng đến hành vi của khung. Quan trọng nhất trong số này là mã nhận dạng ứng dụng Bộ thu web, được dùng để lọc kết quả khám phá và khởi chạy ứng dụng Bộ thu web khi phiên Truyền bắt đầu.

Kotlin
class CastOptionsProvider : OptionsProvider {
    override fun getCastOptions(context: Context): CastOptions {
        return Builder()
            .setReceiverApplicationId(context.getString(R.string.app_id))
            .build()
    }

    override fun getAdditionalSessionProviders(context: Context): List<SessionProvider>? {
        return null
    }
}
Java
public class CastOptionsProvider implements OptionsProvider {
    @Override
    public CastOptions getCastOptions(Context context) {
        CastOptions castOptions = new CastOptions.Builder()
            .setReceiverApplicationId(context.getString(R.string.app_id))
            .build();
        return castOptions;
    }
    @Override
    public List<SessionProvider> getAdditionalSessionProviders(Context context) {
        return null;
    }
}

Bạn phải khai báo tên đủ điều kiện của OptionsProvider được triển khai dưới dạng trường siêu dữ liệu trong tệp AndroidManifest.xml của ứng dụng người gửi:

<application>
    ...
    <meta-data
        android:name=
            "com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
        android:value="com.foo.CastOptionsProvider" />
</application>

CastContext được khởi tạo từng phần khi CastContext.getSharedInstance() được gọi.

Kotlin
class MyActivity : FragmentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        val castContext = CastContext.getSharedInstance(this)
    }
}
Java
public class MyActivity extends FragmentActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        CastContext castContext = CastContext.getSharedInstance(this);
    }
}

Tiện ích trải nghiệm người dùng được truyền

Khung Truyền cung cấp các tiện ích tuân thủ Danh sách kiểm tra thiết kế truyền:

  • Lớp phủ giới thiệu: Khung này cung cấp Thành phần hiển thị tuỳ chỉnh, IntroductoryOverlay, được hiển thị cho người dùng để thu hút sự chú ý đến nút Truyền vào lần đầu tiên có người nhận. Ứng dụng Sender có thể tuỳ chỉnh văn bản và vị trí của văn bản tiêu đề.

  • Nút Truyền: Nút Truyền sẽ xuất hiện khi hệ thống phát hiện thấy một bộ thu hỗ trợ ứng dụng của bạn. Khi người dùng nhấp vào nút Truyền lần đầu tiên, một hộp thoại Truyền sẽ xuất hiện để liệt kê các thiết bị đã phát hiện. Khi người dùng nhấp vào nút Truyền trong khi thiết bị được kết nối, thiết bị sẽ hiển thị siêu dữ liệu đa phương tiện hiện tại (chẳng hạn như tiêu đề, tên phòng thu âm và hình thu nhỏ) hoặc cho phép người dùng ngắt kết nối khỏi thiết bị Truyền.

  • Bộ điều khiển thu nhỏ: Khi người dùng đang truyền nội dung và đã rời khỏi trang nội dung hiện tại hoặc bộ điều khiển mở rộng sang một màn hình khác trong ứng dụng người gửi, trình điều khiển thu nhỏ sẽ hiển thị ở cuối màn hình để cho phép người dùng xem siêu dữ liệu phương tiện truyền hiện tại và điều khiển chế độ phát.

  • Bộ điều khiển mở rộng: Khi người dùng đang truyền nội dung, nếu họ nhấp vào thông báo đa phương tiện hoặc bộ điều khiển nhỏ, thì tay điều khiển mở rộng sẽ chạy và hiển thị siêu dữ liệu nội dung nghe nhìn hiện đang phát, đồng thời cung cấp một số nút để điều khiển việc phát nội dung nghe nhìn.

  • Thông báo: Chỉ dành cho Android. Khi người dùng đang truyền nội dung và rời khỏi ứng dụng người gửi, một thông báo về nội dung nghe nhìn sẽ hiển thị cho biết siêu dữ liệu nội dung nghe nhìn và nội dung điều khiển chế độ phát hiện đang truyền.

  • Màn hình khoá: Chỉ dành cho Android. Khi người dùng đang truyền nội dung và di chuyển (hoặc hết thời gian thiết bị) đến màn hình khoá, một nút điều khiển màn hình khoá nội dung nghe nhìn sẽ hiển thị cho thấy siêu dữ liệu đa phương tiện đang truyền và bộ điều khiển chế độ phát.

Hướng dẫn sau đây bao gồm nội dung mô tả về cách thêm các tiện ích này vào ứng dụng của bạn.

Thêm nút Truyền

API Android MediaRouter được thiết kế để cho phép hiển thị và phát nội dung nghe nhìn trên các thiết bị phụ. Các ứng dụng Android sử dụng API MediaRouter phải bao gồm nút Truyền trong giao diện người dùng để cho phép người dùng chọn tuyến phương tiện để phát nội dung nghe nhìn trên thiết bị phụ như Thiết bị truyền.

Khung này giúp bạn dễ dàng thêm MediaRouteButton dưới dạng một Cast button. Trước tiên, bạn nên thêm một mục trong trình đơn hoặc MediaRouteButton trong tệp xml xác định trình đơn và sử dụng CastButtonFactory để kết nối với mục đó bằng khung.

// To add a Cast button, add the following snippet.
// menu.xml
<item
    android:id="@+id/media_route_menu_item"
    android:title="@string/media_route_menu_title"
    app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
    app:showAsAction="always" />
Kotlin
// Then override the onCreateOptionMenu() for each of your activities.
// MyActivity.kt
override fun onCreateOptionsMenu(menu: Menu): Boolean {
    super.onCreateOptionsMenu(menu)
    menuInflater.inflate(R.menu.main, menu)
    CastButtonFactory.setUpMediaRouteButton(
        applicationContext,
        menu,
        R.id.media_route_menu_item
    )
    return true
}
Java
// Then override the onCreateOptionMenu() for each of your activities.
// MyActivity.java
@Override public boolean onCreateOptionsMenu(Menu menu) {
    super.onCreateOptionsMenu(menu);
    getMenuInflater().inflate(R.menu.main, menu);
    CastButtonFactory.setUpMediaRouteButton(getApplicationContext(),
                                            menu,
                                            R.id.media_route_menu_item);
    return true;
}

Sau đó, nếu Activity kế thừa từ FragmentActivity, thì bạn có thể thêm MediaRouteButton vào bố cục.

// activity_layout.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:gravity="center_vertical"
   android:orientation="horizontal" >

   <androidx.mediarouter.app.MediaRouteButton
       android:id="@+id/media_route_button"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_weight="1"
       android:mediaRouteTypes="user"
       android:visibility="gone" />

</LinearLayout>
Kotlin
// MyActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_layout)

    mMediaRouteButton = findViewById<View>(R.id.media_route_button) as MediaRouteButton
    CastButtonFactory.setUpMediaRouteButton(applicationContext, mMediaRouteButton)

    mCastContext = CastContext.getSharedInstance(this)
}
Java
// MyActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_layout);

   mMediaRouteButton = (MediaRouteButton) findViewById(R.id.media_route_button);
   CastButtonFactory.setUpMediaRouteButton(getApplicationContext(), mMediaRouteButton);

   mCastContext = CastContext.getSharedInstance(this);
}

Để đặt giao diện của nút Truyền bằng cách sử dụng giao diện, hãy xem Tùy chỉnh Nút truyền.

Định cấu hình khám phá thiết bị

Khám phá thiết bị được quản lý hoàn toàn bởi CastContext. Khi khởi chạy CastContext, ứng dụng người gửi sẽ chỉ định Mã nhận dạng ứng dụng Bộ thu web và có thể tuỳ ý yêu cầu lọc không gian tên bằng cách đặt supportedNamespaces trong CastOptions. CastContext sẽ tham chiếu đến MediaRouter nội bộ và sẽ bắt đầu quá trình khám phá khi ứng dụng gửi ở chế độ nền trước và dừng khi ứng dụng người gửi chuyển sang chế độ nền.

Kotlin
class CastOptionsProvider : OptionsProvider {
    companion object {
        const val CUSTOM_NAMESPACE = "urn:x-cast:custom_namespace"
    }

    override fun getCastOptions(appContext: Context): CastOptions {
        val supportedNamespaces: MutableList<String> = ArrayList()
        supportedNamespaces.add(CUSTOM_NAMESPACE)

        return CastOptions.Builder()
            .setReceiverApplicationId(context.getString(R.string.app_id))
            .setSupportedNamespaces(supportedNamespaces)
            .build()
    }

    override fun getAdditionalSessionProviders(context: Context): List<SessionProvider>? {
        return null
    }
}
Java
class CastOptionsProvider implements OptionsProvider {
    public static final String CUSTOM_NAMESPACE = "urn:x-cast:custom_namespace";

    @Override
    public CastOptions getCastOptions(Context appContext) {
        List<String> supportedNamespaces = new ArrayList<>();
        supportedNamespaces.add(CUSTOM_NAMESPACE);

        CastOptions castOptions = new CastOptions.Builder()
            .setReceiverApplicationId(context.getString(R.string.app_id))
            .setSupportedNamespaces(supportedNamespaces)
            .build();
        return castOptions;
    }

    @Override
    public List<SessionProvider> getAdditionalSessionProviders(Context context) {
        return null;
    }
}

Cách hoạt động của tính năng quản lý phiên

SDK Truyền giới thiệu khái niệm về phiên Truyền, là bước thiết lập kết hợp các bước kết nối với thiết bị, khởi chạy (hoặc tham gia) ứng dụng Trình nhận web, kết nối với ứng dụng đó và khởi chạy một kênh điều khiển nội dung phương tiện. Xem Hướng dẫn về vòng đời của ứng dụng dành cho Bộ thu web để biết thêm thông tin về phiên Truyền và vòng đời của Bộ thu web.

Phiên hoạt động do lớp SessionManager quản lý mà ứng dụng của bạn có thể truy cập thông qua CastContext.getSessionManager(). Các phiên riêng lẻ được biểu thị bằng các lớp con của lớp Session. Ví dụ: CastSession biểu thị các phiên bằng thiết bị truyền. Ứng dụng của bạn có thể truy cập phiên Truyền hiện đang hoạt động thông qua SessionManager.getCurrentCastSession().

Ứng dụng của bạn có thể sử dụng lớp SessionManagerListener để theo dõi các sự kiện trong phiên, chẳng hạn như tạo, tạm ngưng, tiếp tục và chấm dứt. Khung này sẽ tự động tìm cách tiếp tục từ khi chấm dứt bất thường/bất ngờ khi một phiên đang hoạt động.

Phiên được tạo và chia nhỏ tự động theo các cử chỉ của người dùng từ hộp thoại MediaRouter.

Nếu cần biết về những thay đổi về trạng thái cho phiên, bạn có thể triển khai SessionManagerListener. Ví dụ này lắng nghe tình trạng sẵn có của CastSession trong Activity.

Kotlin
class MyActivity : Activity() {
    private var mCastSession: CastSession? = null
    private lateinit var mSessionManager: SessionManager
    private val mSessionManagerListener: SessionManagerListener<CastSession> =
        SessionManagerListenerImpl()

    private inner class SessionManagerListenerImpl : SessionManagerListener<CastSession?> {
        override fun onSessionStarted(session: CastSession?, sessionId: String) {
            invalidateOptionsMenu()
        }

        override fun onSessionResumed(session: CastSession?, wasSuspended: Boolean) {
            invalidateOptionsMenu()
        }

        override fun onSessionEnded(session: CastSession?, error: Int) {
            finish()
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mSessionManager = CastContext.getSharedInstance(this).sessionManager
    }

    override fun onResume() {
        super.onResume()
        mCastSession = mSessionManager.currentCastSession
        mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession::class.java)
    }

    override fun onPause() {
        super.onPause()
        mSessionManager.removeSessionManagerListener(mSessionManagerListener, CastSession::class.java)
        mCastSession = null
    }
}
Java
public class MyActivity extends Activity {
    private CastSession mCastSession;
    private SessionManager mSessionManager;
    private SessionManagerListener<CastSession> mSessionManagerListener =
            new SessionManagerListenerImpl();

    private class SessionManagerListenerImpl implements SessionManagerListener<CastSession> {
        @Override
        public void onSessionStarted(CastSession session, String sessionId) {
            invalidateOptionsMenu();
        }
        @Override
        public void onSessionResumed(CastSession session, boolean wasSuspended) {
            invalidateOptionsMenu();
        }
        @Override
        public void onSessionEnded(CastSession session, int error) {
            finish();
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mSessionManager = CastContext.getSharedInstance(this).getSessionManager();
    }
    @Override
    protected void onResume() {
        super.onResume();
        mCastSession = mSessionManager.getCurrentCastSession();
        mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession.class);
    }
    @Override
    protected void onPause() {
        super.onPause();
        mSessionManager.removeSessionManagerListener(mSessionManagerListener, CastSession.class);
        mCastSession = null;
    }
}

Chuyển luồng

Việc giữ nguyên trạng thái phiên là cơ sở của hoạt động chuyển luồng, trong đó người dùng có thể di chuyển các luồng âm thanh và video hiện có trên các thiết bị bằng lệnh thoại, Ứng dụng Google Home hoặc màn hình thông minh. Nội dung nghe nhìn ngừng phát trên một thiết bị (nguồn) và tiếp tục trên một thiết bị khác (đích đến). Mọi thiết bị Truyền có chương trình cơ sở mới nhất đều có thể dùng làm nguồn hoặc đích đến trong quá trình chuyển luồng.

Để có thiết bị đích mới trong quá trình chuyển hoặc mở rộng luồng, hãy đăng ký Cast.Listener bằng cách sử dụng CastSession#addCastListener. Sau đó, hãy gọi CastSession#getCastDevice() trong lệnh gọi lại onDeviceNameChanged.

Hãy xem bài viết Chuyển luồng trên Bộ thu web để biết thêm thông tin.

Kết nối lại tự động

Khung này cung cấp ReconnectionService mà ứng dụng người gửi có thể bật để xử lý việc kết nối lại trong nhiều trường hợp góc mờ, chẳng hạn như:

  • Khôi phục sau khi mất Wi-Fi tạm thời
  • Khôi phục từ chế độ ngủ của thiết bị
  • Khôi phục từ chạy trong nền ứng dụng
  • Khôi phục nếu ứng dụng gặp sự cố

Dịch vụ này được bật theo mặc định và bạn có thể tắt trong CastOptions.Builder.

Dịch vụ này có thể tự động được hợp nhất vào tệp kê khai của ứng dụng nếu bạn đã bật tính năng tự động hợp nhất trong tệp gradle.

Khung này sẽ bắt đầu dịch vụ khi có phiên phát nội dung đa phương tiện và dừng dịch vụ đó khi phiên phát nội dung đa phương tiện kết thúc.

Cách hoạt động của chế độ Điều khiển nội dung nghe nhìn

Khung Truyền không còn dùng lớp RemoteMediaPlayer từ Cast 2.x mà thay vào đó là một lớp mới RemoteMediaClient. Lớp này cung cấp chức năng tương tự trong một nhóm các API thuận tiện hơn và tránh phải truyền ứng dụng GoogleApiClient.

Khi ứng dụng của bạn thiết lập CastSession bằng một ứng dụng Bộ thu web hỗ trợ không gian tên phương tiện, bản sao của RemoteMediaClient sẽ tự động được tạo bởi khung; ứng dụng của bạn có thể truy cập vào không gian đó bằng cách gọi phương thức getRemoteMediaClient() trên thực thể CastSession.

Tất cả các phương thức của RemoteMediaClient gửi yêu cầu đến Web receiver sẽ trả về đối tượng PendingResult, có thể dùng để theo dõi yêu cầu đó.

Theo dự kiến, nhiều phần của ứng dụng có thể chia sẻ bản sao của RemoteMediaClient, thực sự có một số thành phần nội bộ của khung, chẳng hạn như bộ điều khiển tối thiểudịch vụ thông báo. Để đạt được mục tiêu đó, thực thể này hỗ trợ việc đăng ký nhiều thực thể của RemoteMediaClient.Listener.

Thiết lập siêu dữ liệu nghe nhìn

Lớp MediaMetadata đại diện cho thông tin về một mục nội dung đa phương tiện mà bạn muốn Truyền. Ví dụ sau đây sẽ tạo một thực thể MediaMetadata mới của một bộ phim và đặt tiêu đề, tiêu đề phụ và hai hình ảnh.

Kotlin
val movieMetadata = MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE)

movieMetadata.putString(MediaMetadata.KEY_TITLE, mSelectedMedia.getTitle())
movieMetadata.putString(MediaMetadata.KEY_SUBTITLE, mSelectedMedia.getStudio())
movieMetadata.addImage(WebImage(Uri.parse(mSelectedMedia.getImage(0))))
movieMetadata.addImage(WebImage(Uri.parse(mSelectedMedia.getImage(1))))
Java
MediaMetadata movieMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE);

movieMetadata.putString(MediaMetadata.KEY_TITLE, mSelectedMedia.getTitle());
movieMetadata.putString(MediaMetadata.KEY_SUBTITLE, mSelectedMedia.getStudio());
movieMetadata.addImage(new WebImage(Uri.parse(mSelectedMedia.getImage(0))));
movieMetadata.addImage(new WebImage(Uri.parse(mSelectedMedia.getImage(1))));

Hãy xem phần Chọn hình ảnh về việc sử dụng hình ảnh có siêu dữ liệu phương tiện.

Tải nội dung nghe nhìn

Ứng dụng của bạn có thể tải một mục nội dung đa phương tiện, như minh hoạ trong mã sau. Trước tiên, hãy sử dụng MediaInfo.Builder với siêu dữ liệu đa phương tiện để tạo một thực thể MediaInfo. Lấy RemoteMediaClient từ CastSession hiện tại, sau đó tải MediaInfo vào RemoteMediaClient đó. Sử dụng RemoteMediaClient để phát, tạm dừng và điều khiển một ứng dụng trình phát nội dung đa phương tiện đang chạy trên Bộ thu web.

Kotlin
val mediaInfo = MediaInfo.Builder(mSelectedMedia.getUrl())
    .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
    .setContentType("videos/mp4")
    .setMetadata(movieMetadata)
    .setStreamDuration(mSelectedMedia.getDuration() * 1000)
    .build()
val remoteMediaClient = mCastSession.getRemoteMediaClient()
remoteMediaClient.load(MediaLoadRequestData.Builder().setMediaInfo(mediaInfo).build())
Java
MediaInfo mediaInfo = new MediaInfo.Builder(mSelectedMedia.getUrl())
        .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
        .setContentType("videos/mp4")
        .setMetadata(movieMetadata)
        .setStreamDuration(mSelectedMedia.getDuration() * 1000)
        .build();
RemoteMediaClient remoteMediaClient = mCastSession.getRemoteMediaClient();
remoteMediaClient.load(new MediaLoadRequestData.Builder().setMediaInfo(mediaInfo).build());

Ngoài ra, hãy xem mục sử dụng bản nhạc đa phương tiện.

Định dạng video 4K

Để kiểm tra xem định dạng video của nội dung nghe nhìn là gì, hãy sử dụng getVideoInfo() trong MediaStatus để lấy thực thể hiện tại của VideoInfo. Phiên bản này chứa loại định dạng TV HDR, cũng như chiều cao và chiều rộng hiển thị tính bằng pixel. Các biến thể có định dạng 4K được biểu thị bằng các hằng số HDR_TYPE_*.

Thông báo điều khiển từ xa đến nhiều thiết bị

Khi người dùng đang truyền, các thiết bị Android khác trên cùng một mạng sẽ nhận được thông báo cho phép họ điều khiển quá trình phát. Bất kỳ ai nhận được thông báo như vậy đều có thể tắt thông báo cho thiết bị đó trong ứng dụng Cài đặt tại Google > Google Cast > Hiển thị thông báo có nút điều khiển từ xa. (Thông báo bao gồm một lối tắt đến ứng dụng Cài đặt.) Để biết thêm thông tin, hãy xem bài viết Truyền thông báo có nút điều khiển từ xa.

Thêm tay điều khiển nhỏ

Theo Danh sách kiểm tra thiết kế truyền, ứng dụng người gửi phải cung cấp một tuỳ chọn điều khiển cố định gọi là đơn vị điều khiển nhỏ sẽ xuất hiện khi người dùng chuyển từ trang nội dung hiện tại đến một phần khác của ứng dụng người gửi đến lời nhắc hiển thị cho người dùng. Bằng cách nhấn vào bộ điều khiển nhỏ, người dùng có thể quay lại chế độ xem bộ điều khiển mở rộng toàn màn hình của Cast.

Khung này cung cấp Thành phần hiển thị tuỳ chỉnh, MiniControllerFragment. Bạn có thể thêm ở cuối tệp bố cục của từng hoạt động mà bạn muốn hiển thị bộ điều khiển tối thiểu.

<fragment
    android:id="@+id/castMiniController"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:visibility="gone"
    class="com.google.android.gms.cast.framework.media.widget.MiniControllerFragment" />

Khi ứng dụng gửi của bạn đang phát video trực tiếp âm thanh hoặc video, SDK sẽ tự động hiển thị nút phát/dừng thay cho nút phát/tạm dừng trong bộ điều khiển nhỏ.

Để đặt giao diện văn bản cho tiêu đề và tiêu đề phụ của thành phần hiển thị tuỳ chỉnh này và để chọn các nút, hãy xem phần Tuỳ chỉnh trình điều khiển nhỏ.

Thêm bộ điều khiển mở rộng

Danh sách kiểm tra thiết kế của Google Cast yêu cầu ứng dụng người gửi phải cung cấp một bộ điều khiển mở rộng cho nội dung nghe nhìn được truyền. Bộ điều khiển mở rộng là phiên bản toàn bộ của bộ điều khiển nhỏ.

SDK Truyền cung cấp một tiện ích cho bộ điều khiển mở rộng có tên là ExpandedControllerActivity. Đây là lớp trừu tượng mà bạn phải tạo lớp con để thêm nút Truyền.

Trước tiên, hãy tạo một tệp tài nguyên trình đơn mới cho tay điều khiển mở rộng để cung cấp nút Truyền:

<menu xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto">

    <item
            android:id="@+id/media_route_menu_item"
            android:title="@string/media_route_menu_title"
            app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
            app:showAsAction="always"/>

</menu>

Tạo một lớp mới mở rộng ExpandedControllerActivity.

Kotlin
class ExpandedControlsActivity : ExpandedControllerActivity() {
    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        super.onCreateOptionsMenu(menu)
        menuInflater.inflate(R.menu.expanded_controller, menu)
        CastButtonFactory.setUpMediaRouteButton(this, menu, R.id.media_route_menu_item)
        return true
    }
}
Java
public class ExpandedControlsActivity extends ExpandedControllerActivity {
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);
        getMenuInflater().inflate(R.menu.expanded_controller, menu);
        CastButtonFactory.setUpMediaRouteButton(this, menu, R.id.media_route_menu_item);
        return true;
    }
}

Bây giờ, hãy khai báo hoạt động mới của bạn trong tệp kê khai ứng dụng trong thẻ application:

<application>
...
<activity
        android:name=".expandedcontrols.ExpandedControlsActivity"
        android:label="@string/app_name"
        android:launchMode="singleTask"
        android:theme="@style/Theme.CastVideosDark"
        android:screenOrientation="portrait"
        android:parentActivityName="com.google.sample.cast.refplayer.VideoBrowserActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
    </intent-filter>
</activity>
...
</application>

Chỉnh sửa CastOptionsProvider cũng như thay đổi NotificationOptionsCastMediaOptions để đặt hoạt động mục tiêu thành hoạt động mới của bạn:

Kotlin
override fun getCastOptions(context: Context): CastOptions? {
    val notificationOptions = NotificationOptions.Builder()
        .setTargetActivityClassName(ExpandedControlsActivity::class.java.name)
        .build()
    val mediaOptions = CastMediaOptions.Builder()
        .setNotificationOptions(notificationOptions)
        .setExpandedControllerActivityClassName(ExpandedControlsActivity::class.java.name)
        .build()

    return CastOptions.Builder()
        .setReceiverApplicationId(context.getString(R.string.app_id))
        .setCastMediaOptions(mediaOptions)
        .build()
}
Java
public CastOptions getCastOptions(Context context) {
    NotificationOptions notificationOptions = new NotificationOptions.Builder()
            .setTargetActivityClassName(ExpandedControlsActivity.class.getName())
            .build();
    CastMediaOptions mediaOptions = new CastMediaOptions.Builder()
            .setNotificationOptions(notificationOptions)
            .setExpandedControllerActivityClassName(ExpandedControlsActivity.class.getName())
            .build();

    return new CastOptions.Builder()
            .setReceiverApplicationId(context.getString(R.string.app_id))
            .setCastMediaOptions(mediaOptions)
            .build();
}

Cập nhật phương thức LocalPlayerActivity loadRemoteMedia để hiển thị hoạt động mới của bạn khi phương tiện từ xa được tải:

Kotlin
private fun loadRemoteMedia(position: Int, autoPlay: Boolean) {
    val remoteMediaClient = mCastSession?.remoteMediaClient ?: return

    remoteMediaClient.registerCallback(object : RemoteMediaClient.Callback() {
        override fun onStatusUpdated() {
            val intent = Intent(this@LocalPlayerActivity, ExpandedControlsActivity::class.java)
            startActivity(intent)
            remoteMediaClient.unregisterCallback(this)
        }
    })

    remoteMediaClient.load(
        MediaLoadRequestData.Builder()
            .setMediaInfo(mSelectedMedia)
            .setAutoplay(autoPlay)
            .setCurrentTime(position.toLong()).build()
    )
}
Java
private void loadRemoteMedia(int position, boolean autoPlay) {
    if (mCastSession == null) {
        return;
    }
    final RemoteMediaClient remoteMediaClient = mCastSession.getRemoteMediaClient();
    if (remoteMediaClient == null) {
        return;
    }
    remoteMediaClient.registerCallback(new RemoteMediaClient.Callback() {
        @Override
        public void onStatusUpdated() {
            Intent intent = new Intent(LocalPlayerActivity.this, ExpandedControlsActivity.class);
            startActivity(intent);
            remoteMediaClient.unregisterCallback(this);
        }
    });
    remoteMediaClient.load(new MediaLoadRequestData.Builder()
            .setMediaInfo(mSelectedMedia)
            .setAutoplay(autoPlay)
            .setCurrentTime(position).build());
}

Khi ứng dụng gửi của bạn đang phát video trực tiếp âm thanh hoặc video, SDK sẽ tự động hiển thị nút phát/dừng thay cho nút phát/tạm dừng trong bộ điều khiển mở rộng.

Để đặt giao diện bằng giao diện, hãy chọn các nút để hiển thị và thêm nút tuỳ chỉnh, xem phần Tuỳ chỉnh Bộ điều khiển mở rộng.

Chỉnh âm lượng

Khung này sẽ tự động quản lý âm lượng cho ứng dụng gửi. Khung này sẽ tự động đồng bộ hoá ứng dụng người gửi và Bộ thu web để giao diện người dùng người gửi luôn báo cáo âm lượng do Bộ nhận web chỉ định.

Điều chỉnh âm lượng của nút vật lý

Trên Android, các nút vật lý trên thiết bị gửi có thể được dùng để thay đổi âm lượng của phiên Truyền trên Bộ thu web theo mặc định cho mọi thiết bị sử dụng Jelly Bean hoặc phiên bản mới hơn.

Kiểm soát âm lượng của nút vật lý trước Jelly Bean

Để sử dụng các phím âm lượng thực để kiểm soát âm lượng của thiết bị nhận tín hiệu trên web trên các thiết bị Android cũ hơn Jelly Bean, ứng dụng người gửi sẽ ghi đè dispatchKeyEvent trong Hoạt động và gọi CastContext.onDispatchVolumeKeyEventBeforeJellyBean():

Kotlin
class MyActivity : FragmentActivity() {
    override fun dispatchKeyEvent(event: KeyEvent): Boolean {
        return (CastContext.getSharedInstance(this)
            .onDispatchVolumeKeyEventBeforeJellyBean(event)
                || super.dispatchKeyEvent(event))
    }
}
Java
class MyActivity extends FragmentActivity {
    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        return CastContext.getSharedInstance(this)
            .onDispatchVolumeKeyEventBeforeJellyBean(event)
            || super.dispatchKeyEvent(event);
    }
}

Thêm chế độ điều khiển nội dung nghe nhìn vào thông báo và màn hình khoá

Chỉ trên Android, Danh sách kiểm tra về thiết kế Google Cast yêu cầu ứng dụng người gửi triển khai các chế độ điều khiển nội dung phương tiện trong thông báo và trên màn hình khoá mà người gửi đang truyền nhưng ứng dụng người gửi không lấy được tiêu điểm. Khung nền tảng cung cấp MediaNotificationServiceMediaIntentReceiver để giúp ứng dụng xác minh người gửi điều khiển nội dung nghe nhìn trong thông báo và trên màn hình khóa.

MediaNotificationService chạy khi người gửi đang truyền và sẽ hiển thị thông báo có hình thu nhỏ hình ảnh và thông tin về mục truyền hiện tại, nút phát/tạm dừng và nút dừng.

MediaIntentReceiver là một BroadcastReceiver xử lý các hành động của người dùng từ thông báo.

Ứng dụng của bạn có thể định cấu hình tính năng thông báo và điều khiển nội dung nghe nhìn từ màn hình khoá thông qua NotificationOptions. Ứng dụng của bạn có thể định cấu hình các nút điều khiển để hiển thị trong thông báo và Activity nào sẽ mở khi người dùng nhấn vào thông báo đó. Nếu các thao tác không được cung cấp rõ ràng, các giá trị mặc định, MediaIntentReceiver.ACTION_TOGGLE_PLAYBACKMediaIntentReceiver.ACTION_STOP_CASTING sẽ được sử dụng.

Kotlin
// Example showing 4 buttons: "rewind", "play/pause", "forward" and "stop casting".
val buttonActions: MutableList<String> = ArrayList()
buttonActions.add(MediaIntentReceiver.ACTION_REWIND)
buttonActions.add(MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK)
buttonActions.add(MediaIntentReceiver.ACTION_FORWARD)
buttonActions.add(MediaIntentReceiver.ACTION_STOP_CASTING)

// Showing "play/pause" and "stop casting" in the compat view of the notification.
val compatButtonActionsIndices = intArrayOf(1, 3)

// Builds a notification with the above actions. Each tap on the "rewind" and "forward" buttons skips 30 seconds.
// Tapping on the notification opens an Activity with class VideoBrowserActivity.
val notificationOptions = NotificationOptions.Builder()
    .setActions(buttonActions, compatButtonActionsIndices)
    .setSkipStepMs(30 * DateUtils.SECOND_IN_MILLIS)
    .setTargetActivityClassName(VideoBrowserActivity::class.java.name)
    .build()
Java
// Example showing 4 buttons: "rewind", "play/pause", "forward" and "stop casting".
List<String> buttonActions = new ArrayList<>();
buttonActions.add(MediaIntentReceiver.ACTION_REWIND);
buttonActions.add(MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK);
buttonActions.add(MediaIntentReceiver.ACTION_FORWARD);
buttonActions.add(MediaIntentReceiver.ACTION_STOP_CASTING);

// Showing "play/pause" and "stop casting" in the compat view of the notification.
int[] compatButtonActionsIndices = new int[]{1, 3};

// Builds a notification with the above actions. Each tap on the "rewind" and "forward" buttons skips 30 seconds.
// Tapping on the notification opens an Activity with class VideoBrowserActivity.
NotificationOptions notificationOptions = new NotificationOptions.Builder()
    .setActions(buttonActions, compatButtonActionsIndices)
    .setSkipStepMs(30 * DateUtils.SECOND_IN_MILLIS)
    .setTargetActivityClassName(VideoBrowserActivity.class.getName())
    .build();

Chế độ hiển thị các nút điều khiển nội dung nghe nhìn từ thông báo và màn hình khoá được bật theo mặc định và có thể tắt bằng cách gọi setNotificationOptions bằng giá trị rỗng trong CastMediaOptions.Builder. Hiện tại, tính năng màn hình khoá được bật ngay khi bạn bật thông báo.

Kotlin
// ... continue with the NotificationOptions built above
val mediaOptions = CastMediaOptions.Builder()
    .setNotificationOptions(notificationOptions)
    .build()
val castOptions: CastOptions = Builder()
    .setReceiverApplicationId(context.getString(R.string.app_id))
    .setCastMediaOptions(mediaOptions)
    .build()
Java
// ... continue with the NotificationOptions built above
CastMediaOptions mediaOptions = new CastMediaOptions.Builder()
        .setNotificationOptions(notificationOptions)
        .build();
CastOptions castOptions = new CastOptions.Builder()
        .setReceiverApplicationId(context.getString(R.string.app_id))
        .setCastMediaOptions(mediaOptions)
        .build();

Khi ứng dụng gửi của bạn đang phát video trực tiếp âm thanh hoặc video, SDK sẽ tự động hiển thị nút phát/dừng thay cho nút phát/tạm dừng trên nút điều khiển thông báo nhưng không hiển thị nút điều khiển trên màn hình khóa.

Lưu ý: Để hiển thị các tùy chọn điều khiển màn hình khóa trên thiết bị sử dụng Lollipop, RemoteMediaClient sẽ tự động thay mặt bạn yêu cầu quyền phát âm thanh.

Xử lý lỗi

Điều quan trọng là các ứng dụng gửi phải xử lý tất cả các lệnh gọi lại lỗi và quyết định phản hồi tốt nhất cho từng giai đoạn trong vòng đời của Truyền. Ứng dụng có thể hiển thị hộp thoại lỗi cho người dùng hoặc có thể quyết định chia nhỏ kết nối với Bộ thu web.