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 trình phát Android bằng SDK Trình phát Android.
Thiết bị di động hoặc máy tính xách tay là thiết bị gửi (sender) có chức năng điều khiển việc phát và thiết bị Google Cast là thiết bị nhận (receiver) hiển thị nội dung trên TV.
Khung gửi đề cập đến tệp nhị phân thư viện lớp Cast và các tài nguyên liên kết có trong thời gian chạy trên trình gửi. Ứng dụng gửi hoặc Ứng dụng Cast là một ứng dụng cũng chạy trên thiết bị gửi. Ứng dụng Web Receiver (Trình thu web) đề cập đến ứng dụng HTML chạy trên thiết bị hỗ trợ Cast.
Khung trình 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 trình gửi về các sự kiện và chuyển đổi giữa nhiều trạng thái 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 thông thường cho ứng dụng Android của trình gửi:
- Khung Cast tự động bắt đầu quá trình khám phá thiết bị
MediaRouter
dựa trên vòng đờiActivity
. - 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 Web Receiver trên thiết bị Truyền.
- Khung này gọi lệnh gọi lại trong ứng dụng gửi để xác nhận rằng ứng dụng Web Receiver đã được khởi chạy.
- Khung này tạo một kênh giao tiếp giữa ứng dụng 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 Web Receiver.
- Khung này đồng bộ hoá trạng thái phát nội dung nghe nhìn giữa trình 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 trình 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 Web Receiver và khi Web Receiver gửi thông tin cập nhật 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 trình gửi.
- Khi người dùng nhấp vào nút Truyền để ngắt kết nối với thiết bị Truyền, khung sẽ ngắt kết nối ứng dụng gửi với Trình 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 của Google Cast, hãy xem Tài liệu tham khảo API Trình gửi Google Cast dành 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 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 độ tối thiểu là API cấp 23 và mục tiêu là API cấp 34.
<uses-sdk
android:minSdkVersion="23"
android:targetSdkVersion="34" />
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 là phiên bản trước Lollipop.
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/Theme.AppCompat" >
...
</application>
Khởi chạy ngữ cảnh truyền
Khung này có một đối tượng singleton toàn cục, CastContext
, điều phối mọi 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 chạy 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ã nhận dạng ứng dụng Web Receiver. Mã này được dùng để lọc kết quả khám phá và chạy ứng dụng Web Receiver khi bắt đầu một phiên truyền.
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 } }
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.
class MyActivity : FragmentActivity() { override fun onCreate(savedInstanceState: Bundle?) { val castContext = CastContext.getSharedInstance(this) } }
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 tính năng Truyền
Khung Cast cung cấp các tiện ích tuân thủ Danh sách kiểm tra thiết kế Cast:
Lớp phủ giới thiệu: Khung này cung cấp một 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 sẽ hiển thị 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, trong đó 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ị đ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 hãng thu âm và hình thu nhỏ) hoặc cho phép người dùng ngắt kết nối vớ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 gọn: 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 mở rộng bộ điều khiển sang một màn hình khác trong ứng dụng gửi, bộ điều khiển thu gọn 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 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, thì bộ đ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 trình truyền, một thông báo nội dung nghe nhìn sẽ hiển thị, 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 phát.
Màn hình khoá: chỉ dành cho 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 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 nội dung nghe nhìn đang truyền và các nút điều khiển phát.
Hướng dẫn sau đây bao gồm nội dung mô tả cách thêm các tiện ích này vào ứng dụng.
Thêm nút Truyền
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 đa phương tiện để phát nội dung đa phương tiệ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 thêm một
MediaRouteButton
dưới dạng
Cast button
một cách dễ dàng. 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 trình đơn đó 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" />
// 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 }
// 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>
// 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) }
// 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); }
Để thiết lập giao diện của nút Truyền bằng 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ị được quản lý hoàn toàn bằng CastContext
.
Khi khởi chạy CastContext, ứng dụng gửi sẽ chỉ định mã nhận dạng ứng dụng Web Receiver và có thể tuỳ ý yêu cầu lọc không gian tên bằng cách đặt supportedNamespaces
trong CastOptions
.
CastContext
giữ một tệp tham chiếu đến MediaRouter
trong nội bộ và sẽ bắt đầu quy trình khám phá theo các điều kiện sau:
- Dựa trên thuật toán được thiết kế để cân bằng độ trễ phát hiện thiết bị và mức sử dụng pin, đôi khi quá trình phát hiện sẽ tự động bắt đầu khi ứng dụng 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ẽ bị dừng khi hộp thoại Truyền bị đóng hoặc ứng dụng gửi chuyển sang chế độ nền.
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 } }
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 phát giới thiệu khái niệm về phiên truyền phát, trong đó việ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 Web Receiver, kết nối với ứng dụng đó và khởi chạy kênh điều khiển nội dung đa phương tiện. Hãy xem Hướng dẫn về vòng đời ứng dụng của Trình thu phát trên web để biết thêm thông tin về các phiên truyền và vòng đời của Trình thu phát trên web.
Các phiên được quản lý bởi 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 với thiết bị Truyền. Ứng dụng của bạn có thể truy cập vào phiên Cast 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 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 quá trình 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ác cử chỉ của người dùng từ hộp thoại MediaRouter
.
Để hiểu rõ hơn về lỗi bắt đầu truyền, ứng dụng có thể sử 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 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 tình trạng có sẵn của CastSession
trong Activity
.
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) } }
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 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ó trên các thiết bị bằng cách sử dụ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ẽ ngừ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). Mọi thiết bị Cast có phần mềm cơ sở mới nhất đều có thể đóng vai trò là nguồn hoặc đích trong quá trình truyền trực tuyến.
Để lấy 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 CastSession#addCastListener
.
Sau đó, hãy gọi CastSession#getCastDevice()
trong lệnh gọi lại onDeviceNameChanged
.
Hãy xem phần Chuyển luồng trên Web Receiver để 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 gửi có thể bật để xử lý việc kết nối lại trong nhiều trường hợp khó xử, 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 sau khi thiết bị chuyển sang chế độ ngủ
- Khôi phục từ trạng thái chạy ứng dụng ở 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à 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 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 không dùng nữa lớp RemoteMediaPlayer
từ Cast 2.x mà thay vào đó là 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 Web Receiver hỗ trợ không gian tên nội dung đa phương tiện, khung sẽ tự động tạo một thực thể của RemoteMediaClient
; ứ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 Trình thu 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, nhiều phần của ứng dụng và một số thành phần nội bộ của khung, chẳng hạn như trình điều khiển mini ổn định và dịch vụ thông báo có thể chia sẻ thực thể của RemoteMediaClient
.
Do đó, 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 nội dung nghe nhìn
Lớp MediaMetadata
đại diện cho 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.
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))))
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 Chọn hình ảnh về việc 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 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 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 chạy trên Web Receiver.
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())
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 kênh nội dung nghe nhì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 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 điều khiển từ xa cho nhiều thiết bị
Khi người dùng 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 để họ cũng có thể điều khiển quá trình phát. Bất kỳ ai có thiết bị 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ông báo đ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 chi tiết, hãy xem phần Thông báo về điều khiển từ xa của tính năng Truyền.
Thêm tay điều khiển mini
Theo Danh sách kiểm tra thiết kế Cast, ứng dụng gửi phải cung cấp một chế độ điều khiển liên tục được gọi là trình điều khiển mini. Chế độ 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 gửi. Trình điều khiển mini sẽ nhắc người dùng về phiên Cast hiện tại. Bằng cách nhấn vào tay điều khiển thu nhỏ, người dùng có thể quay lại chế độ xem tay điều khiển mở rộng ở chế độ toàn màn hình của tính năng Truyền.
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 mỗi hoạt động mà bạn muốn hiển thị trình điều khiển mini.
<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 hoặc sự kiện phát trực tiếp âm thanh, SDK sẽ tự động hiển thị nút phát/dừng thay vì nút phát/tạm dừng trong tay điều khiển mini.
Để đặt giao diện văn bản của tiêu đề và phụ đề của thành phần hiển thị tuỳ chỉnh này, cũng như để chọn nút, hãy xem phần Tuỳ chỉnh Tay điều khiển mini.
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 cung cấp một trình điều khiển mở rộng cho nội dung đa phương tiệ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 thu nhỏ.
SDK truyền cung cấp một tiện ích cho tay đ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 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
.
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 } }
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:
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() }
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 tải nội dung nghe nhìn từ xa:
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() ) }
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 hoặc luồng âm thanh trực tiếp, SDK sẽ tự động hiển thị nút phát/dừng thay vì nút phát/tạm dừng trong bộ điều khiển mở rộng.
Để thiết lập 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, hãy xem phần Tuỳ chỉnh tay đ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 gửi. Khung này tự động đồng bộ hoá ứng dụng gửi và ứng dụng Web Receiver để giao diện người dùng của ứng dụng gửi luôn báo cáo âm lượng do Web Receiver chỉ định.
Điều khiển âm lượng bằng nút vật lý
Trên Android, bạn có thể sử 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 thu 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 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ị Web Receiver trên các thiết bị Android cũ hơn Jelly Bean, ứng dụng gửi phải ghi đè dispatchKeyEvent
trong các Hoạt động và gọi CastContext.onDispatchVolumeKeyEventBeforeJellyBean()
:
class MyActivity : FragmentActivity() { override fun dispatchKeyEvent(event: KeyEvent): Boolean { return (CastContext.getSharedInstance(this) .onDispatchVolumeKeyEventBeforeJellyBean(event) || super.dispatchKeyEvent(event)) } }
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 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à trong màn hình khoá, trong đó ứng dụng gửi đang truyền nhưng ứng dụng gửi không có tiêu điểm. Khung này cung cấp MediaNotificationService
và MediaIntentReceiver
để giúp ứng dụng gửi tạo 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á.
MediaNotificationService
chạy khi người gửi đang truyền và sẽ hiển thị một 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 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à đ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 những 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 thao tác, 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
.
// 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()
// 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, tính năng 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à bạn có thể tắt tính năng này 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ẽ bật miễn là bạn bật thông báo.
// ... 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()
// ... 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 hoặc sự kiện phát trực tiếp âm thanh, SDK sẽ tự động hiển thị nút phát/dừng thay vì nút phát/tạm dừng trên chế độ điều khiển thông báo nhưng không phải chế độ điều khiển màn hình khoá.
Lưu ý: Để hiển thị các nút điều khiển màn hình khoá trên các thiết bị chạy phiên bản trước 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 rất quan trọng là các ứng dụng gửi phải xử lý tất 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 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 Trình thu web.