Tích hợp Cast 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à thiết bị gửi điều khiển quá trình phát, còn thiết bị Google Cast là thiết bị nhận hiển thị nội dung trên TV.

Khung người gửi là tệp nhị phân của thư viện lớp Cast 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 của người gửi hoặc ứng dụng Truyền là ứng dụng cũng chạy trên thiết bị của người gửi. Ứng dụng Trình nhậ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ế lệnh 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 nhiều trạng thái trong vòng đời của ứng dụng Truyền.

Luồng ứng dụng

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

  • Khung truyền tự động bắt đầu quá trình khám phá thiết bị MediaRouter 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 cùng với danh sách các thiết bị Truyền được 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 Trình thu nhận web trên thiết bị Truyền.
  • Khung này gọi các lệnh gọi lại trong ứng dụng của người gửi để xác nhận rằng ứng dụng Trình nhận web đã được khởi chạy.
  • Khung này tạo ra một kênh giao tiếp giữa ứng dụng gửi và ứng dụng Trình nhận web.
  • Khung này sử dụng kênh giao tiếp để tải và điều khiển việc phát nội dung nghe nhìn trên Trình thu nhận web.
  • Khung này đồng bộ hoá trạng thái phát nội dung nghe nhìn giữa người gửi và Bộ thu web: khi người dùng thực hiện các thao tác trên giao diện người dùng của người gửi, khung này 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 thông tin cập nhật về trạng thái nội dung nghe nhìn, khung này sẽ cập nhật trạng thái của giao diện người dùng của 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 của người gửi khỏi Trình nhận web.

Để xem 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 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 định cấu hình các phần tử sau cho SDK Truyền:

uses-sdk

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

<uses-sdk
        android:minSdkVersion="24"
        android:targetSdkVersion="35" />

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 của riêng mình, bạn nên sử dụng một biến thể của Theme.AppCompat khi nhắm đến phiên bản SDK Android tối thiểu trước Lollipop.

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

Khởi động Cast Context

Khung này có một đối tượng singleton chung, CastContext, điều phối tất cả các hoạt động 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 động singleton CastContext. OptionsProvider cung cấp một thực thể của CastOptions chứa các tuỳ chọn ảnh hưởng đến hành vi của khung. Quan trọng nhất trong số này là mã ứng dụng Web Receiver, được dùng để lọc kết quả khám phá và chạy ứng dụng Web Receiver khi một 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 đã triển khai dưới dạng trường siêu dữ liệu trong tệp AndroidManifest.xml của ứng dụng 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ủa Cast

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 một Chế độ xem tuỳ chỉnh, IntroductoryOverlay, được hiển thị cho người dùng để thu hút sự chú ý đến nút Truyền trong lần đầu tiên có thiết bị thu. Ứng dụng của người gửi 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 bất kể có thiết bị Truyền hay không. 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ị được phát hiện. Khi người dùng nhấp vào nút Truyền trong khi thiết bị đang kết nối, nút này sẽ hiển thị siêu dữ liệu nội dung nghe nhìn hiện tại (chẳng hạn như tiêu đề, tên của 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. "Nút Truyền" đôi khi được gọi là "biểu tượng Truyền".

  • Trình đ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 trình điều khiển mở rộng để chuyển sang một màn hình khác trong ứng dụng của người gửi, trình điều khiển thu nhỏ sẽ xuất hiện ở cuối màn hình để cho phép người dùng xem siêu dữ liệu nội dung nghe nhìn đang truyền và điều khiển chế độ phát.

  • Trình đ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 nội dung nghe nhìn hoặc trình điều khiển thu nhỏ, thì trình điều khiển mở rộng sẽ khởi chạy, hiển thị siêu dữ liệu nội dung nghe nhìn đang phát và 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 gửi, một thông báo về nội dung nghe nhìn sẽ xuất hiện, cho thấy siêu dữ liệu của nội dung nghe nhìn đang truyền và bộ điều khiển chế độ phát.

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

Hướng dẫn sau đây mô tả 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

Các API MediaRouter của Android đượ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 có nút Truyền trong giao diện người dùng để cho phép người dùng chọn một tuyến nội dung nghe nhìn để phát nội dung nghe nhìn trên một thiết bị phụ, chẳng hạn như thiết bị Truyền.

Khung này giúp bạn dễ dàng thêm MediaRouteButton làm Cast button. Trước tiên, bạn nên thêm một mục trong trình đơn hoặc một MediaRouteButton trong tệp xml xác định trình đơn của bạn, rồi sử dụng CastButtonFactory để kết nối mục đó với 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, 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 một giao diện, hãy xem phần Tuỳ chỉnh nút Truyền.

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

Tính năng khám phá thiết bị hoàn toàn do CastContext quản lý. Khi khởi chạy CastContext, ứng dụng của người gửi sẽ chỉ định mã ứng dụng của Trình nhận web và có thể yêu cầu lọc không gian tên (không bắt buộc) bằng cách đặt supportedNamespaces trong CastOptions. CastContext lưu giữ thông tin tham chiếu đến MediaRouter trong nội bộ và sẽ bắt đầu quy trình khám phá trong các điều kiện sau:

  • Dựa trên thuật toán được thiết kế để cân bằng độ trễ khi khám phá thiết bị và mức sử dụng pin, quá trình khám phá đôi khi sẽ tự động bắt đầu khi ứng dụng của người gửi chuyển sang nền trước.
  • Hộp thoại Truyền đang mở.
  • SDK Truyền đang cố gắng khôi phục một phiên Truyền.

Quá trình khám phá sẽ dừng khi hộp thoại Truyền đóng hoặc ứng dụng 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, việc thiết lập phiên này 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 kênh điều khiển nội dung nghe nhìn. Hãy xem Hướng dẫn về vòng đời ứng dụng của Trình thu nhận web để biết thêm thông tin về phiên truyền và vòng đời của Trình thu nhận web.

Các phiên được quản lý bằng lớp SessionManager 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 đại diện cho các phiên hoạt động với thiết bị Truyền. Ứng dụng của bạn có thể truy cập vào 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 tự động cố gắng tiếp tục từ một phiên chấm dứt bất thường/đột ngột trong khi phiên đó đang hoạt động.

Các phiên được tạo và huỷ tự động để phản hồi cử chỉ của người dùng từ hộp thoại MediaRouter.

Để hiểu rõ hơn về lỗi khi bắt đầu Cast, các ứng dụng có thể sử dụng CastContext#getCastReasonCodeForCastStatusCode(int) để chuyển đổi lỗi khi bắt đầu phiên thành CastReasonCodes. Xin lưu ý rằng một số lỗi khi bắt đầu phiên (ví dụ: CastReasonCodes#CAST_CANCELLED) là hành vi dự kiến và không được ghi lại dưới dạng lỗi.

Nếu cần biết các thay đổi về trạng thái của phiên, bạn có thể triển khai SessionManagerListener. Ví dụ này theo dõi khả năng sử dụng của CastSession trong Activity.

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

    private inner class SessionManagerListenerImpl : SessionManagerListener<CastSession?> {
        override fun onSessionStarting(session: CastSession?) {}

        override fun onSessionStarted(session: CastSession?, sessionId: String) {
            invalidateOptionsMenu()
        }

        override fun onSessionStartFailed(session: CastSession?, error: Int) {
            val castReasonCode = mCastContext.getCastReasonCodeForCastStatusCode(error)
            // Handle error
        }

        override fun onSessionSuspended(session: CastSession?, reason Int) {}

        override fun onSessionResuming(session: CastSession?, sessionId: String) {}

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

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

        override fun onSessionEnding(session: CastSession?) {}

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

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mCastContext = CastContext.getSharedInstance(this)
        mSessionManager = mCastContext.sessionManager
        mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession::class.java)
    }

    override fun onResume() {
        super.onResume()
        mCastSession = mSessionManager.currentCastSession
    }

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

    private class SessionManagerListenerImpl implements SessionManagerListener<CastSession> {
        @Override
        public void onSessionStarting(CastSession session) {}
        @Override
        public void onSessionStarted(CastSession session, String sessionId) {
            invalidateOptionsMenu();
        }
        @Override
        public void onSessionStartFailed(CastSession session, int error) {
            int castReasonCode = mCastContext.getCastReasonCodeForCastStatusCode(error);
            // Handle error
        }
        @Override
        public void onSessionSuspended(CastSession session, int reason) {}
        @Override
        public void onSessionResuming(CastSession session, String sessionId) {}
        @Override
        public void onSessionResumed(CastSession session, boolean wasSuspended) {
            invalidateOptionsMenu();
        }
        @Override
        public void onSessionResumeFailed(CastSession session, int error) {}
        @Override
        public void onSessionEnding(CastSession session) {}
        @Override
        public void onSessionEnded(CastSession session, int error) {
            finish();
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mCastContext = CastContext.getSharedInstance(this);
        mSessionManager = mCastContext.getSessionManager();
        mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession.class);
    }

    @Override
    protected void onResume() {
        super.onResume();
        mCastSession = mSessionManager.getCurrentCastSession();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mSessionManager.removeSessionManagerListener(mSessionManagerListener, CastSession.class);
    }
}

Chuyển đổi phiên phát trực tuyến

Việc duy trì trạng thái phiên là cơ sở của tính năng chuyển đổi phiên phát trực tuyến, 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 sẽ dừng phát trên một thiết bị (nguồn) và tiếp tục phát trên một thiết bị khác (đích). Bất kỳ thiết bị Truyền nào có chương trình cơ sở mới nhất đều có thể đóng vai trò là nguồn hoặc đích đến trong quá trình chuyển đổi phiên phát trực tuyến.

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

Hãy xem phần Chuyển đổi phiên phát trực tuyến trên Trình thu nhận web để biết thêm thông tin.

Tự động kết nối lại

Khung này cung cấp một ReconnectionService mà ứng dụng của 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 đặc biệt, chẳng hạn như:

  • Khôi phục sau khi tạm thời mất kết nối Wi-Fi
  • Khôi phục từ chế độ ngủ của thiết bị
  • Khôi phục sau khi ứng dụng chạy ở chế độ nền
  • 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 phần CastOptions.Builder.

Dịch vụ này có thể tự động 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 nghe nhìn và dừng dịch vụ khi phiên phát nội dung nghe nhìn kết thúc.

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

Khung Cast ngừng sử dụng lớp RemoteMediaPlayer từ Cast 2.x để chuyển sang lớp mới RemoteMediaClient, lớp này cung cấp chức năng tương tự trong một tập hợp API thuận tiện hơn và tránh phải truyền vào GoogleApiClient.

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

Tất cả phương thức của RemoteMediaClient đưa ra yêu cầu cho Bộ thu web sẽ trả về một đối tượng PendingResult có thể dùng để theo dõi yêu cầu đó.

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

Đặt siêu dữ liệu nội dung nghe nhìn

Lớp MediaMetadata biểu thị thông tin về một mục nội dung nghe nhìn mà bạn muốn Truyền. Ví dụ sau đây tạo một thực thể MediaMetadata mới của một bộ phim và đặt 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))));

Xem phần Lựa chọn hình ảnh về cách sử dụng hình ảnh có siêu dữ liệu nội dung nghe nhì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 nghe nhìn, như trong đoạn mã sau. Trước tiên, hãy sử dụng MediaInfo.Builder cùng với siêu dữ liệu của nội dung nghe nhì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 ứng dụng trình phát nội dung nghe nhìn đang chạy trên Trình 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 phần về cách sử dụng bản nhạc đa phương tiện.

Định dạng video 4K

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

Thông báo có nút điều khiển từ xa cho 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ạ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 có thiết bị nhận được những 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ông báo điều khiển từ xa. (Thông báo này có 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 Thông báo có nút điều khiển từ xa của Cast.

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

Theo Danh sách kiểm tra thiết kế Cast, ứng dụng của người gửi phải cung cấp một chế độ điều khiển liên tục được gọi là bộ điều khiển thu nhỏ. Bộ điều khiển này sẽ xuất hiện khi người dùng rời khỏi trang nội dung hiện tại để chuyển đến một phần khác của ứng dụng người gửi. Bộ điều khiển thu nhỏ cung cấp một lời nhắc trực quan cho người dùng về phiên truyền hiện tại. Bằng cách nhấn vào bộ điều khiển thu 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 một Chế độ xem tuỳ chỉnh, MiniControllerFragment, mà bạn có thể thêm vào cuối tệp bố cục của mỗi hoạt động mà bạn muốn hiển thị bộ điều khiển thu nhỏ.

<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 đang phát một sự kiện phát trực tiếp video hoặc âm thanh, 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 thu nhỏ.

Để đặt giao diện văn bản của tiêu đề và phụ đề của chế độ xem tuỳ chỉnh này, đồng thời chọn các nút, hãy xem phần Tuỳ chỉnh Trình điều khiển thu 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 của 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 đang được truyền. Trình điều khiển mở rộng là phiên bản toàn màn hình của trình điều khiển thu nhỏ.

SDK truyền cung cấp một tiện ích cho trình điều khiển mở rộng có tên là ExpandedControllerActivity. Đây là một lớp trừu tượng mà bạn phải phân lớp để 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 bộ đ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 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 và thay đổi NotificationOptions và CastMediaOptions để đặt hoạt động mục tiêu thành hoạt động mới:

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 khi nội dung nghe nhì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 đang phát một sự kiện phát trực tiếp video hoặc âm thanh, 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 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.

Điều chỉnh âm lượng

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

Điều khiển âm lượng bằng nút vật lý

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

Điều khiển âm lượng bằng nút vật lý trước phiên bản Jelly Bean

Để sử dụng các phím âm lượng vật lý nhằm điều khiển âm lượng của thiết bị Web Receiver trên các thiết bị Android cũ hơn Jelly Bean, ứng dụng của người gửi phải ghi đè dispatchKeyEvent trong Hoạt động của họ 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 các nút đ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 thiết kế Google Cast yêu cầu ứng dụng của người gửi phải triển khai các nút điều khiển nội dung nghe nhìn trong thông báo và trong màn hình khoá, nơi người gửi đang truyền nhưng ứng dụng của người gửi không được lấy tiêu điểm. Khung này cung cấp MediaNotificationService và MediaIntentReceiver để giúp ứng dụng của người gửi tạo các nút điều khiển nội dung nghe nhìn trong thông báo và trên màn hình khoá.

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ỏ của ảnh và thông tin về mục đang truyền, nút phát/tạm dừng và nút dừng.

MediaIntentReceiver là một BroadcastReceiver xử lý các thao tác 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 thông báo và các nút đ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 sẽ 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 bạn không cung cấp rõ ràng các hành động, thì hệ thống sẽ sử dụng các giá trị mặc định là MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK và MediaIntentReceiver.ACTION_STOP_CASTING.

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

Theo mặc định, 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á sẽ được bật và có thể tắt bằng cách gọi setNotificationOptions với giá trị rỗng trong CastMediaOptions.Builder. Hiện tại, tính năng màn hình khoá sẽ được bật miễn là thông báo được bật.

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 đang phát một sự kiện phát trực tiếp video hoặc âm thanh, 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ị trên nút điều khiển màn hình khoá.

Lưu ý: Để hiển thị các chế độ điều khiển màn hình khoá trên các thiết bị trước Lollipop, RemoteMediaClient sẽ tự động yêu cầu quyền phát âm thanh thay cho bạn.

Xử lý lỗi

Ứng dụng của người gửi cần 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 phù hợp nhất cho từng giai đoạn của vòng đời 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 huỷ kết nối với Bộ thu web.