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 người gửi Android bằng cách sử dụng Android Sender SDK.

Thiết bị di động hoặc máy tính xách tay là người gửi điều khiển quá trình phát, còn thiết bị Google Cast là 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 Cast và các tài nguyên liên quan xuất hiện trong thời gian chạy trên người gửi. Ứng dụng gửi hoặc ứng dụng Truyền đề cập đến một ứng dụng cũng chạy trên người gửi. Ứng dụng Web receiver 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 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 Truyền.

Luồng ứng dụng

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

  • Khung Truyền sẽ tự động bắt đầu quá trình khám phá thiết bị MediaRouter dựa trên vòng đời của 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 thập dữ liệu web trên Thiết bị truyền.
  • Khung này sẽ gọi các lệnh gọi lại trong ứng dụng người gửi để xác nhận rằng ứng dụng Trình nhận web đã được chạy.
  • Khung này tạo một kênh liên lạc giữa người 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à kiểm soát việc phát nội dung nghe nhìn trên Trình nhận web.
  • Khung này đồng bộ hoá trạng thái phát nội dung đa phương tiện giữa người gửi và Web receiver: 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 đa phương tiện đó đến Web receiver và khi Web receiver gửi nội dung cập nhật trạng thái nội dung nghe nhìn, khung 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 thu nhận 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 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 để 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 đây cho Cast SDK:

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, API cấp độ tối thiểu là API cấp 21 và mục tiêu là API cấp độ 28.

<uses-sdk
        android:minSdkVersion="21"
        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 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 một phiên bản SDK Android tối thiểu là 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 toàn cục là CastContext để điều phối tất cả các lượt 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 để khởi chạy singleton CastContext. OptionsProvider cung cấp một thực thể của CastOptions có 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 Trình thu nhận web, dùng để lọc kết quả khám phá và chạy ứng dụng Trình 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 đã 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 động 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 Cast UX

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 Khung hiển thị tuỳ chỉnh, IntroductoryOverlay, 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ó bộ thu. Ứ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 hiển thị bất kể thiết bị Truyền có sẵn hay không. Trong lần đầu người dùng nhấp vào nút Truyền, hộp thoại Truyền sẽ hiển thị để 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, 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 phòng thu 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".

  • Bộ điều khiển thu nhỏ: Khi người dùng đang truyền nội dung và đã di chuyển từ 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 dành cho người gửi, bộ điều khiển nhỏ sẽ hiển thị ở 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.

  • 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 nội dung nghe nhìn hoặc bộ điều khiển mini, bộ điều khiển mở rộng sẽ 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 chế độ 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 nội dung nghe nhìn sẽ hiển thị cho biết siêu dữ liệu nội dung nghe nhìn hiện đang truyền và các bộ điều khiển chế độ phát.

  • Màn hình khoá: Chỉ Android. Khi người dùng đang truyền nội dung và điều hướng (hoặc thiết bị hết thời gian chờ) đến màn hình khoá, một tuỳ chọn điều khiển màn hình khoá nội dung đa phương tiện sẽ hiển thị để cho thấy siêu dữ liệu nội dung đ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 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

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 bao gồm nút Truyền trong giao diện người dùng, nhằm 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 việc thêm MediaRouteButton dưới dạng Cast button rất dễ dàng. Trước tiên, bạn nên thêm một mục trong trình đơn hoặc MediaRouteButton vào tệp xml dùng để xác định trình đơn và sử dụng CastButtonFactory để kết nối mục này 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 của bạn kế thừa từ FragmentActivity, bạn có thể thêm MediaRouteButton vào bố cục của mình.

// 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 cho nút Truyền bằng giao diện, hãy xem bài viết 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ị được CastContext quản lý hoàn toàn. Khi khởi chạy CastContext, ứng dụng gửi sẽ chỉ định mã ứng dụng Trình nhận web và có thể tùy ý yêu cầu lọc không gian tên bằng cách đặt supportedNamespaces trong CastOptions. CastContext lưu giữ một tham chiếu đến MediaRouter trong nội bộ và sẽ bắt đầu quá trình khám phá trong các điều kiện sau:

  • Dựa trên một thuật toán được thiết kế để cân bằng giữa độ trễ 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ở.
  • Cast SDK đang cố gắng khôi phục một phiên Truyền.

Quá trình khám phá sẽ dừng lại khi hộp thoại Truyền đóng hoặc ứng dụng gửi chuyển sang chạy trong 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

Cast SDK giới thiệu khái niệm về phiên Truyền, quy trình 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 thu 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. Xem Hướng dẫn về vòng đời của ứng dụng của Bộ thu web để biết thêm thông tin về các phiên Truyền và vòng đời Bộ thu web.

Các phiên hoạt động đượ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 có thiết bị truyền. Ứng dụng của bạn có thể truy cập vào phiên Truyền đang hoạt độ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 của 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ừ một trường hợp chấm dứt bất thường/đột ngột trong khi một phiên đang hoạt động.

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

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

Nếu cần nắm được 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 tình trạng sẵn có 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
    }

    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 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();
    }
    @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 duy trì trạng thái phiên là cơ sở của quá trình 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ó giữa 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 dừ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ể đóng vai trò là nguồn hoặc đích đến trong quá trình chuyển luồng.

Để nhận 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.

Vui lòng xem bài viết Chuyển luồng trên Bộ thu 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 góc nhỏ, 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 sau khi chuyển ứng dụng sang 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 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ó một phiên phát nội dung đa phương tiện và dừng 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 đa phương tiện

Khung Cast ngừng sử dụng lớp RemoteMediaPlayer từ Cast 2.x và thay vào đó là một lớp RemoteMediaClient mới, cung cấp chức năng tương tự trong một tập hợp các 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 CastSession bằng một ứng dụng Web receiver 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 ra; ứng dụng của bạn có thể truy cập vào đó 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 Trình nhận 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 nhiều phần trong ứng dụng chia sẻ 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ợ đăng ký nhiều thực thể của RemoteMediaClient.Listener.

Thiết lập siêu dữ liệu đa phương tiện

Lớp MediaMetadata biểu thị thông tin về mục nội dung đa phương tiện mà bạn muốn Truyền. Ví dụ sau đây tạo một phiên bản MediaMetadata mới của phim và đặt tiêu đề, phụ đề cũng như 2 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 với siêu dữ liệu đa 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 nghe nhì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 của nội dung nghe nhìn để tạo một phiên bản 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 hoặc điều khiển ứng dụng trình phát nội dung đa phương tiện chạy trên Bộ thu trên 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 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 đa phương tiện, hãy sử dụng getVideoInfo() trong MediaStatus để nhận thực thể hiện tại của VideoInfo. Thực thể này chứa loại định dạng TV HDR và chiều cao và chiều rộng hiển thị tính bằng pixel. Các biến thể của định dạng 4K được biểu thị bằng các hằng số HDR_TYPE_*.

Điều khiển từ xa thông báo 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ột mạng cũng sẽ nhận được thông báo cho phép họ điều khiển chế độ phát. Bất kỳ ai có thiết bị nhận được cá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ông báo điều khiển từ xa. (Các thông báo bao gồm lối tắt đến ứng dụng Cài đặt.) Để biết thêm thông tin chi tiết, hãy xem phần Truyền thông báo điều khiển từ xa.

Thêm tay điều khiển mini

Theo Danh sách kiểm tra thiết kế truyền, ứng dụng dành cho người gửi sẽ cung cấp quyền kiểm soát lâu dài gọi là bộ điều khiển nhỏ. Bộ điều khiển này sẽ xuất hiện khi người dùng di chuyển từ trang nội dung hiện tại đến một phần khác của ứng dụng gửi. Trình điều khiển thu nhỏ sẽ hiển thị lời nhắc 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 của tính năng Truyền toàn màn hình.

Khung này cung cấp một Khung hiển thị tuỳ chỉnh, MiniControllerFragment mà bạn có thể thêm vào 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 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 của người gửi đang phá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 cho tiêu đề và tiêu đề phụ của thành phần hiển thị tuỳ chỉnh này, cũng như để chọn các nút, hãy xem phần Tuỳ chỉnh tay điều khiển nhỏ.

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

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

Cast SDK 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 thêm 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 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 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 và thay đổi NotificationOptions cũng như 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 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 của người gửi đang phá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 các giao diện, chọn nút sẽ hiển thị và thêm các nút tuỳ chỉnh, xem Tuỳ chỉnh trình điều khiển mở rộng.

Chỉnh âm lượng

Khung này tự động quản lý âm lượng cho ứng dụng người gửi. Khung tự động đồng bộ hoá ứng dụng 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 trên web chỉ định.

Điều chỉnh âm lượng bằng nút vật lý

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

Điều chỉnh âm lượng của nút vật lý trước 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 gửi sẽ ghi đè dispatchKeyEvent trong Activity (Hoạt động) của các thiết bị đó 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á

Riêng 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 chế độ điều khiển nội dung nghe nhìn trong thông báo và trên 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ó tiêu điểm. Khung này cung cấp MediaNotificationServiceMediaIntentReceiver để giúp ứng dụng của người gửi tạo các chế độ điều khiển nội dung nghe nhìn trong một thông báo và trong 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 kèm theo 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 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à chế độ đ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 nút điều khiển nào 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 thao tác không được cung cấp rõ ràng, thì hệ thống sẽ sử dụng các giá trị mặc định, MediaIntentReceiver.ACTION_TOGGLE_PLAYBACKMediaIntentReceiver.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();

Tính năng hiển thị các tuỳ chọn đ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 có giá trị rỗng trong CastMediaOptions.Builder. Hiện tại, tính năng màn hình khoá sẽ bật miễn là 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 của người gửi đang phát video phát trực tiếp 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 các nút điều khiển màn hình khoá trên thiết bị chạy phiên bản 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

Điều quan trọng là ứng dụng gửi thư 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 Cast. Ứng dụng có thể cho người dùng thấy hộp thoại lỗi hoặc có thể quyết định chia nhỏ kết nối với Trình nhận web.