1. Tổng quan

Lớp học lập trình này sẽ hướng dẫn bạn cách sửa đổi một ứng dụng video hiện có trên Android để truyền nội dung trên một thiết bị có hỗ trợ Google Cast.
Google Cast là gì?
Google Cast cho phép người dùng truyền nội dung từ thiết bị di động lên TV. Sau đó, người dùng có thể dùng thiết bị di động làm điều khiển từ xa để phát nội dung nghe nhìn trên TV.
Google Cast SDK giúp bạn mở rộng ứng dụng để điều khiển TV hoặc hệ thống âm thanh. Cast SDK cho phép bạn thêm các thành phần giao diện người dùng cần thiết dựa trên Danh sách kiểm tra thiết kế của Google Cast.
Danh sách kiểm tra thiết kế Google Cast được cung cấp để giúp trải nghiệm người dùng Cast trở nên đơn giản và dễ đoán trên tất cả các nền tảng được hỗ trợ.
Chúng ta sẽ xây dựng những gì?
Khi hoàn tất lớp học lập trình này, bạn sẽ có một ứng dụng video dành cho thiết bị Android có thể truyền video đến một thiết bị hỗ trợ Google Cast.
Kiến thức bạn sẽ học được
- Cách thêm Google Cast SDK vào một ứng dụng video mẫu.
- Cách thêm nút Truyền để chọn một thiết bị truyền.
- Cách kết nối với thiết bị truyền và chạy trình nhận nội dung nghe nhìn.
- Cách truyền video.
- Cách thêm bộ điều khiển thu nhỏ của Cast vào ứng dụng.
- Cách hỗ trợ thông báo về nội dung nghe nhìn và các chế độ điều khiển trên màn hình khoá.
- Cách thêm bộ điều khiển mở rộng.
- Cách cung cấp lớp phủ giới thiệu.
- Cách tuỳ chỉnh tiện ích Cast.
- Cách tích hợp với Cast Connect
Bạn cần có
- SDK Android mới nhất.
- Android Studio phiên bản 3.2 trở lên
- Một thiết bị di động chạy Android 4.1 trở lên (Jelly Bean) (cấp độ API 16).
- Cáp dữ liệu USB để kết nối thiết bị di động với máy tính bạn dùng để phát triển.
- Một thiết bị Google Cast, chẳng hạn như Chromecast hoặc Android TV được thiết lập để có quyền truy cập vào Internet.
- TV hoặc màn hình có cổng đầu vào HDMI.
- Bạn cần có Chromecast có Google TV để kiểm thử việc tích hợp Cast Connect, nhưng không bắt buộc đối với phần còn lại của Lớp học lập trình. Nếu không có, bạn có thể bỏ qua bước Thêm tính năng hỗ trợ Cast Connect ở gần cuối hướng dẫn này.
Trải nghiệm
- Bạn cần có kiến thức về Kotlin và phát triển Android.
- Bạn cũng cần có kiến thức về cách xem TV :)
Bạn sẽ sử dụng hướng dẫn này như thế nào?
Bạn đánh giá thế nào về trải nghiệm của mình khi tạo ứng dụng Android?
Bạn đánh giá thế nào về trải nghiệm xem truyền hình?
2. Nhận mã mẫu
Bạn có thể tải tất cả mã mẫu xuống máy tính...
và giải nén tệp zip đã tải xuống.
3. Chạy ứng dụng mẫu

Trước tiên, hãy xem ứng dụng mẫu hoàn chỉnh trông như thế nào. Ứng dụng này là một trình phát video cơ bản. Người dùng có thể chọn một video trong danh sách rồi phát video đó trên thiết bị hoặc truyền video đó đến một thiết bị truyền Google Cast.
Sau khi tải mã xuống, hãy làm theo các hướng dẫn sau để mở và chạy ứng dụng mẫu hoàn chỉnh trong Android Studio:
Chọn Import Project (Nhập dự án) trên màn hình chào mừng hoặc chọn File > New > Import Project... (Tệp > Mới > Nhập dự án...) trong trình đơn.
Chọn thư mục 
app-done trong thư mục mã mẫu rồi nhấp vào OK.
Nhấp vào File >
Sync Project with Gradle Files (Tệp >
Đồng bộ hoá dự án với các tệp Gradle).
Bật tính năng gỡ lỗi qua USB trên thiết bị Android – theo mặc định, trên Android 4.2 trở lên, màn hình Tùy chọn cho nhà phát triển sẽ bị ẩn. Để hiển thị số điện thoại, hãy chuyển đến phần Cài đặt > Giới thiệu về điện thoại rồi nhấn vào Số bản dựng bảy lần. Quay lại màn hình trước, chuyển đến phần Hệ thống > Nâng cao rồi nhấn vào Tùy chọn cho nhà phát triển ở gần dưới cùng, sau đó nhấn vào Gỡ lỗi qua USB để bật tính năng này.
Cắm thiết bị Android rồi nhấp vào nút
Chạy trong Android Studio. Sau vài giây, bạn sẽ thấy ứng dụng video có tên Truyền video xuất hiện.
Nhấp vào nút Truyền trong ứng dụng video rồi chọn thiết bị truyền Google Cast.
Chọn một video rồi nhấp vào nút phát.
Video sẽ bắt đầu phát trên thiết bị Google Cast.
Bộ điều khiển mở rộng sẽ xuất hiện. Bạn có thể dùng nút phát/tạm dừng để điều khiển quá trình phát.
Quay lại danh sách video.
Giờ đây, bạn sẽ thấy một bộ điều khiển thu nhỏ ở cuối màn hình. 
Nhấp vào nút tạm dừng trong bộ điều khiển thu nhỏ để tạm dừng video trên thiết bị nhận. Nhấp vào nút phát trong bộ điều khiển thu nhỏ để tiếp tục phát lại video.
Nhấp vào nút màn hình chính trên thiết bị di động. Kéo thông báo xuống, bạn sẽ thấy thông báo về phiên truyền.
Khoá điện thoại, sau đó mở khoá. Lúc này, bạn sẽ thấy một thông báo trên màn hình khoá để điều khiển chế độ phát nội dung nghe nhìn hoặc dừng truyền.
Quay lại ứng dụng video rồi nhấp vào nút Truyền để dừng truyền trên thiết bị truyền Google Cast.
Câu hỏi thường gặp
4. Chuẩn bị dự án khởi đầu

Bạn cần thêm tính năng hỗ trợ Google Cast vào ứng dụng khởi động mà bạn đã tải xuống. Sau đây là một số thuật ngữ về Google Cast mà chúng ta sẽ sử dụng trong lớp học lập trình này:
- ứng dụng người gửi chạy trên thiết bị di động hoặc máy tính xách tay,
- ứng dụng receiver chạy trên thiết bị truyền Google Cast.
Bây giờ, bạn đã sẵn sàng xây dựng dựa trên dự án khởi đầu bằng Android Studio:
- Chọn thư mục

app-starttrong tệp tải xuống mã mẫu (Chọn Import Project (Nhập dự án) trên màn hình chào mừng hoặc chọn mục File > New > Import Project... (Tệp > Mới > Nhập dự án...) trong trình đơn). - Nhấp vào nút
Đồng bộ hoá dự án với tệp Gradle. - Nhấp vào nút
Run (Chạy) để chạy ứng dụng và khám phá giao diện người dùng.
Thiết kế ứng dụng
Ứng dụng tìm nạp danh sách video từ một máy chủ web từ xa và cung cấp danh sách để người dùng duyệt xem. Người dùng có thể chọn một video để xem thông tin chi tiết hoặc phát video đó trên thiết bị di động.
Ứng dụng này bao gồm 2 hoạt động chính: VideoBrowserActivity và LocalPlayerActivity. Để tích hợp chức năng Google Cast, Hoạt động cần kế thừa từ AppCompatActivity hoặc FragmentActivity mẹ của nó. Hạn chế này tồn tại vì chúng ta cần thêm MediaRouteButton (có trong thư viện hỗ trợ MediaRouter) làm MediaRouteActionProvider và điều này sẽ chỉ hoạt động nếu hoạt động đang kế thừa từ các lớp nêu trên. Thư viện hỗ trợ MediaRouter phụ thuộc vào Thư viện hỗ trợ AppCompat cung cấp các lớp bắt buộc.
VideoBrowserActivity
Hoạt động này chứa một Fragment (VideoBrowserFragment). Danh sách này được hỗ trợ bởi một ArrayAdapter (VideoListAdapter). Danh sách video và siêu dữ liệu liên quan được lưu trữ trên một máy chủ từ xa dưới dạng tệp JSON. AsyncTaskLoader (VideoItemLoader) sẽ tìm nạp JSON này và xử lý để tạo danh sách các đối tượng MediaItem.
Đối tượng MediaItem mô hình hoá một video và siêu dữ liệu liên quan, chẳng hạn như tiêu đề, nội dung mô tả, URL cho luồng phát, URL cho hình ảnh hỗ trợ và các bản phụ đề liên quan (nếu có). Đối tượng MediaItem được truyền giữa các hoạt động, vì vậy MediaItem có các phương thức tiện ích để chuyển đổi đối tượng này thành Bundle và ngược lại.
Khi trình tải tạo danh sách MediaItems, trình tải sẽ truyền danh sách đó đến VideoListAdapter. Sau đó, VideoListAdapter sẽ trình bày danh sách MediaItems trong VideoBrowserFragment. Người dùng sẽ thấy một danh sách hình thu nhỏ của video kèm theo nội dung mô tả ngắn gọn cho từng video. Khi một mục được chọn, MediaItem tương ứng sẽ được chuyển đổi thành Bundle và được truyền đến LocalPlayerActivity.
LocalPlayerActivity
Hoạt động này hiển thị siêu dữ liệu về một video cụ thể và cho phép người dùng phát video đó trên thiết bị di động.
Hoạt động này có một VideoView, một số nút điều khiển nội dung nghe nhìn và một vùng văn bản để hiện nội dung mô tả của video đã chọn. Trình phát sẽ che phần trên cùng của màn hình, chừa chỗ cho phần mô tả chi tiết của video bên dưới. Người dùng có thể phát/tạm dừng hoặc tua video phát cục bộ.
Phần phụ thuộc
Vì đang dùng AppCompatActivity, nên chúng ta cần thư viện hỗ trợ AppCompat. Để quản lý danh sách video và nhận hình ảnh cho danh sách một cách không đồng bộ, chúng ta sẽ dùng thư viện Volley.
Câu hỏi thường gặp
5. Thêm nút Truyền

Ứng dụng hỗ trợ Cast sẽ hiển thị nút Truyền trong mỗi hoạt động của ứng dụng. Khi nhấp vào nút Truyền, người dùng sẽ thấy danh sách các thiết bị truyền mà họ có thể chọn. Nếu người dùng đang phát nội dung cục bộ trên thiết bị gửi, thì việc chọn một thiết bị truyền sẽ bắt đầu hoặc tiếp tục phát trên thiết bị truyền đó. Bất cứ lúc nào trong phiên truyền, người dùng có thể nhấp vào nút Truyền và dừng truyền ứng dụng của bạn đến thiết bị truyền. Người dùng phải có thể kết nối hoặc ngắt kết nối với thiết bị Truyền trong khi đang thực hiện bất kỳ hoạt động nào của ứng dụng, như mô tả trong Danh sách kiểm tra thiết kế của Google Cast.
Phần phụ thuộc
Cập nhật tệp build.gradle của ứng dụng để thêm các phần phụ thuộc thư viện cần thiết:
dependencies {
implementation 'androidx.appcompat:appcompat:1.5.0'
implementation 'androidx.mediarouter:mediarouter:1.3.1'
implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation 'com.google.android.gms:play-services-cast-framework:21.1.0'
implementation 'com.android.volley:volley:1.2.1'
implementation "androidx.core:core-ktx:1.8.0"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
}
Đồng bộ hoá dự án để xác nhận dự án tạo bản dựng mà không gặp lỗi.
Khởi chạy
Khung Cast có một đối tượng singleton chung là CastContext, giúp điều phối mọi hoạt động tương tác với Cast.
Bạn phải triển khai giao diện OptionsProvider để cung cấp CastOptions cần thiết để khởi chạy singleton CastContext. Lựa chọn quan trọng nhất là mã ứng dụng nhận. Mã này được dùng để lọc kết quả phát hiện thiết bị Cast và để chạy ứng dụng nhận khi một phiên Cast bắt đầu.
Khi phát triển ứng dụng của riêng mình có hỗ trợ Cast, bạn phải đăng ký làm nhà phát triển Cast rồi lấy mã ứng dụng cho ứng dụng của mình. Trong lớp học lập trình này, chúng ta sẽ sử dụng một mã ứng dụng mẫu.
Thêm tệp CastOptionsProvider.kt mới sau đây vào gói com.google.sample.cast.refplayer của dự án:
package com.google.sample.cast.refplayer
import android.content.Context
import com.google.android.gms.cast.framework.OptionsProvider
import com.google.android.gms.cast.framework.CastOptions
import com.google.android.gms.cast.framework.SessionProvider
class CastOptionsProvider : OptionsProvider {
override fun getCastOptions(context: Context): CastOptions {
return CastOptions.Builder()
.setReceiverApplicationId(context.getString(R.string.app_id))
.build()
}
override fun getAdditionalSessionProviders(context: Context): List<SessionProvider>? {
return null
}
}
Bây giờ, hãy khai báo OptionsProvider trong thẻ "application" của tệp AndroidManifest.xml ứng dụng:
<meta-data
android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
android:value="com.google.sample.cast.refplayer.CastOptionsProvider" />
Khởi chạy CastContext một cách gián tiếp trong phương thức VideoBrowserActivity onCreate:
import com.google.android.gms.cast.framework.CastContext
private var mCastContext: CastContext? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.video_browser)
setupActionBar()
mCastContext = CastContext.getSharedInstance(this)
}
Thêm cùng một logic khởi tạo vào LocalPlayerActivity.
Nút truyền
Giờ đây, sau khi CastContext được khởi chạy, chúng ta cần thêm nút Truyền để cho phép người dùng chọn một thiết bị truyền. Nút Truyền được triển khai bằng MediaRouteButton từ thư viện hỗ trợ MediaRouter. Giống như mọi biểu tượng thao tác mà bạn có thể thêm vào hoạt động của mình (bằng cách sử dụng ActionBar hoặc Toolbar), trước tiên, bạn cần thêm mục trong trình đơn tương ứng vào trình đơn của mình.
Chỉnh sửa tệp res/menu/browse.xml và thêm mục MediaRouteActionProvider vào trình đơn trước mục cài đặt:
<item
android:id="@+id/media_route_menu_item"
android:title="@string/media_route_menu_title"
app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
app:showAsAction="always"/>
Ghi đè phương thức onCreateOptionsMenu() của VideoBrowserActivity bằng cách sử dụng CastButtonFactory để kết nối MediaRouteButton với khung Cast:
import com.google.android.gms.cast.framework.CastButtonFactory
private var mediaRouteMenuItem: MenuItem? = null
override fun onCreateOptionsMenu(menu: Menu): Boolean {
super.onCreateOptionsMenu(menu)
menuInflater.inflate(R.menu.browse, menu)
mediaRouteMenuItem = CastButtonFactory.setUpMediaRouteButton(getApplicationContext(), menu,
R.id.media_route_menu_item)
return true
}
Ghi đè onCreateOptionsMenu trong LocalPlayerActivity theo cách tương tự.
Nhấp vào nút
Run (Chạy) để chạy ứng dụng trên thiết bị di động. Bạn sẽ thấy nút Truyền trong thanh thao tác của ứng dụng. Khi bạn nhấp vào nút này, danh sách các thiết bị truyền trên mạng cục bộ sẽ xuất hiện. CastContext sẽ tự động quản lý tính năng phát hiện thiết bị. Chọn thiết bị truyền và ứng dụng nhận mẫu sẽ tải trên thiết bị truyền. Bạn có thể di chuyển giữa hoạt động duyệt xem và hoạt động của trình phát cục bộ, đồng thời trạng thái của nút Truyền sẽ luôn được đồng bộ hoá.
Chúng tôi chưa thiết lập bất kỳ tính năng hỗ trợ nào cho việc phát nội dung nghe nhìn, vì vậy, bạn chưa thể phát video trên thiết bị truyền. Nhấp vào nút Truyền để ngắt kết nối.
6. Truyền nội dung video

Chúng ta sẽ mở rộng ứng dụng mẫu để phát video từ xa trên thiết bị truyền. Để làm được điều đó, chúng ta cần theo dõi nhiều sự kiện do khung Cast tạo ra.
Truyền nội dung nghe nhìn
Nhìn chung, nếu muốn phát nội dung nghe nhìn trên một thiết bị truyền, bạn cần làm những việc sau:
- Tạo một đối tượng
MediaInfomô hình hoá một mục nội dung nghe nhìn. - Kết nối với thiết bị truyền và chạy ứng dụng nhận.
- Tải đối tượng
MediaInfovào bộ nhận và phát nội dung. - Theo dõi trạng thái của nội dung nghe nhìn.
- Gửi lệnh phát đến bộ nhận dựa trên hoạt động tương tác của người dùng.
Chúng ta đã thực hiện Bước 2 trong phần trước. Bạn có thể dễ dàng thực hiện bước 3 bằng khung truyền. Bước 1 là ánh xạ một đối tượng sang một đối tượng khác; MediaInfo là thứ mà khung Cast hiểu được và MediaItem là hoạt động đóng gói của ứng dụng cho một mục nội dung nghe nhìn; chúng ta có thể dễ dàng ánh xạ một MediaItem sang một MediaInfo.
Ứng dụng mẫu LocalPlayerActivity đã phân biệt giữa chế độ phát cục bộ và chế độ phát từ xa bằng cách sử dụng enum này:
private var mLocation: PlaybackLocation? = null
enum class PlaybackLocation {
LOCAL, REMOTE
}
enum class PlaybackState {
PLAYING, PAUSED, BUFFERING, IDLE
}
Trong lớp học lập trình này, bạn không cần phải hiểu chính xác cách hoạt động của tất cả logic trong trình phát mẫu. Điều quan trọng cần lưu ý là bạn sẽ phải sửa đổi trình phát nội dung nghe nhìn của ứng dụng để nhận biết hai vị trí phát theo cách tương tự.
Hiện tại, trình phát cục bộ luôn ở trạng thái phát cục bộ vì trình phát này chưa biết gì về trạng thái Truyền. Chúng ta cần cập nhật giao diện người dùng dựa trên các quá trình chuyển đổi trạng thái diễn ra trong khung Cast. Ví dụ: nếu bắt đầu truyền, chúng ta cần dừng phát cục bộ và tắt một số nút điều khiển. Tương tự, nếu dừng truyền khi đang ở trong hoạt động này, chúng ta cần chuyển sang chế độ phát cục bộ. Để xử lý việc đó, chúng ta cần theo dõi nhiều sự kiện do khung Cast tạo ra.
Quản lý phiên truyền
Đối với khung truyền, một phiên truyền 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), kết nối với ứng dụng nhận và khởi tạo kênh điều khiển nội dung nghe nhìn (nếu thích hợp). Kênh điều khiển nội dung nghe nhìn là cách khung truyền gửi và nhận thông báo từ trình phát nội dung nghe nhìn của bộ nhận.
Phiên truyền sẽ tự động bắt đầu khi người dùng chọn một thiết bị thông qua nút Truyền và sẽ tự động dừng khi người dùng ngắt kết nối. Cast SDK cũng tự động xử lý việc kết nối lại với một phiên nhận do sự cố về mạng.
Hãy thêm SessionManagerListener vào LocalPlayerActivity:
import com.google.android.gms.cast.framework.CastSession
import com.google.android.gms.cast.framework.SessionManagerListener
...
private var mSessionManagerListener: SessionManagerListener<CastSession>? = null
private var mCastSession: CastSession? = null
...
private fun setupCastListener() {
mSessionManagerListener = object : SessionManagerListener<CastSession> {
override fun onSessionEnded(session: CastSession, error: Int) {
onApplicationDisconnected()
}
override fun onSessionResumed(session: CastSession, wasSuspended: Boolean) {
onApplicationConnected(session)
}
override fun onSessionResumeFailed(session: CastSession, error: Int) {
onApplicationDisconnected()
}
override fun onSessionStarted(session: CastSession, sessionId: String) {
onApplicationConnected(session)
}
override fun onSessionStartFailed(session: CastSession, error: Int) {
onApplicationDisconnected()
}
override fun onSessionStarting(session: CastSession) {}
override fun onSessionEnding(session: CastSession) {}
override fun onSessionResuming(session: CastSession, sessionId: String) {}
override fun onSessionSuspended(session: CastSession, reason: Int) {}
private fun onApplicationConnected(castSession: CastSession) {
mCastSession = castSession
if (null != mSelectedMedia) {
if (mPlaybackState == PlaybackState.PLAYING) {
mVideoView!!.pause()
loadRemoteMedia(mSeekbar!!.progress, true)
return
} else {
mPlaybackState = PlaybackState.IDLE
updatePlaybackLocation(PlaybackLocation.REMOTE)
}
}
updatePlayButton(mPlaybackState)
invalidateOptionsMenu()
}
private fun onApplicationDisconnected() {
updatePlaybackLocation(PlaybackLocation.LOCAL)
mPlaybackState = PlaybackState.IDLE
mLocation = PlaybackLocation.LOCAL
updatePlayButton(mPlaybackState)
invalidateOptionsMenu()
}
}
}
Trong hoạt động LocalPlayerActivity, chúng tôi muốn được thông báo khi kết nối hoặc ngắt kết nối với thiết bị truyền để có thể chuyển sang hoặc chuyển từ trình phát cục bộ. Xin lưu ý rằng không chỉ phiên bản ứng dụng đang chạy trên thiết bị di động của bạn mới có thể làm gián đoạn kết nối, mà một phiên bản khác của ứng dụng (hoặc một ứng dụng khác) đang chạy trên một thiết bị di động khác cũng có thể làm gián đoạn kết nối.
Bạn có thể truy cập vào phiên hiện đang hoạt động dưới dạng SessionManager.getCurrentSession(). Các phiên được tạo và huỷ tự động để phản hồi các hoạt động tương tác của người dùng với hộp thoại Truyền.
Chúng ta cần đăng ký trình nghe phiên và khởi tạo một số biến mà chúng ta sẽ sử dụng trong hoạt động này. Thay đổi phương thức LocalPlayerActivity onCreate thành:
import com.google.android.gms.cast.framework.CastContext
...
private var mCastContext: CastContext? = null
...
override fun onCreate(savedInstanceState: Bundle?) {
...
mCastContext = CastContext.getSharedInstance(this)
mCastSession = mCastContext!!.sessionManager.currentCastSession
setupCastListener()
...
loadViews()
...
val bundle = intent.extras
if (bundle != null) {
....
if (shouldStartPlayback) {
....
} else {
if (mCastSession != null && mCastSession!!.isConnected()) {
updatePlaybackLocation(PlaybackLocation.REMOTE)
} else {
updatePlaybackLocation(PlaybackLocation.LOCAL)
}
mPlaybackState = PlaybackState.IDLE
updatePlayButton(mPlaybackState)
}
}
...
}
Đang tải nội dung nghe nhìn
Trong Cast SDK, RemoteMediaClient cung cấp một tập hợp các API thuận tiện để quản lý việc phát nội dung nghe nhìn từ xa trên bộ nhận. Đối với CastSession hỗ trợ phát nội dung nghe nhìn, SDK sẽ tự động tạo một thực thể RemoteMediaClient. Bạn có thể truy cập vào đối tượng này bằng cách gọi phương thức getRemoteMediaClient() trên thực thể CastSession. Thêm các phương thức sau vào LocalPlayerActivity để tải video hiện được chọn trên bộ nhận:
import com.google.android.gms.cast.framework.media.RemoteMediaClient
import com.google.android.gms.cast.MediaInfo
import com.google.android.gms.cast.MediaLoadOptions
import com.google.android.gms.cast.MediaMetadata
import com.google.android.gms.common.images.WebImage
import com.google.android.gms.cast.MediaLoadRequestData
private fun loadRemoteMedia(position: Int, autoPlay: Boolean) {
if (mCastSession == null) {
return
}
val remoteMediaClient = mCastSession!!.remoteMediaClient ?: return
remoteMediaClient.load( MediaLoadRequestData.Builder()
.setMediaInfo(buildMediaInfo())
.setAutoplay(autoPlay)
.setCurrentTime(position.toLong()).build())
}
private fun buildMediaInfo(): MediaInfo? {
val movieMetadata = MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE)
mSelectedMedia?.studio?.let { movieMetadata.putString(MediaMetadata.KEY_SUBTITLE, it) }
mSelectedMedia?.title?.let { movieMetadata.putString(MediaMetadata.KEY_TITLE, it) }
movieMetadata.addImage(WebImage(Uri.parse(mSelectedMedia!!.getImage(0))))
movieMetadata.addImage(WebImage(Uri.parse(mSelectedMedia!!.getImage(1))))
return mSelectedMedia!!.url?.let {
MediaInfo.Builder(it)
.setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
.setContentType("videos/mp4")
.setMetadata(movieMetadata)
.setStreamDuration((mSelectedMedia!!.duration * 1000).toLong())
.build()
}
}
Giờ đây, hãy cập nhật nhiều phương thức hiện có để sử dụng logic phiên Cast nhằm hỗ trợ tính năng phát từ xa:
private fun play(position: Int) {
startControllersTimer()
when (mLocation) {
PlaybackLocation.LOCAL -> {
mVideoView!!.seekTo(position)
mVideoView!!.start()
}
PlaybackLocation.REMOTE -> {
mPlaybackState = PlaybackState.BUFFERING
updatePlayButton(mPlaybackState)
//seek to a new position within the current media item's new position
//which is in milliseconds from the beginning of the stream
mCastSession!!.remoteMediaClient?.seek(position.toLong())
}
else -> {}
}
restartTrickplayTimer()
}
private fun togglePlayback() {
...
PlaybackState.IDLE -> when (mLocation) {
...
PlaybackLocation.REMOTE -> {
if (mCastSession != null && mCastSession!!.isConnected) {
loadRemoteMedia(mSeekbar!!.progress, true)
}
}
else -> {}
}
...
}
override fun onPause() {
...
mCastContext!!.sessionManager.removeSessionManagerListener(
mSessionManagerListener!!, CastSession::class.java)
}
override fun onResume() {
Log.d(TAG, "onResume() was called")
mCastContext!!.sessionManager.addSessionManagerListener(
mSessionManagerListener!!, CastSession::class.java)
if (mCastSession != null && mCastSession!!.isConnected) {
updatePlaybackLocation(PlaybackLocation.REMOTE)
} else {
updatePlaybackLocation(PlaybackLocation.LOCAL)
}
super.onResume()
}
Đối với phương thức updatePlayButton, hãy thay đổi giá trị của biến isConnected:
private fun updatePlayButton(state: PlaybackState?) {
...
val isConnected = (mCastSession != null
&& (mCastSession!!.isConnected || mCastSession!!.isConnecting))
...
}
Bây giờ, hãy nhấp vào nút
Chạy để chạy ứng dụng trên thiết bị di động. Kết nối với thiết bị truyền và bắt đầu phát video. Bạn sẽ thấy video phát trên thiết bị nhận.
7. Bộ điều khiển mini
Danh sách kiểm tra thiết kế Cast yêu cầu tất cả ứng dụng Cast phải cung cấp một bộ điều khiển thu nhỏ xuất hiện khi người dùng rời khỏi trang nội dung hiện tại. Bộ điều khiển thu nhỏ giúp bạn truy cập tức thì và nhắc nhở bạn về phiên Cast hiện tại.

Cast SDK cung cấp một khung hiển thị tuỳ chỉnh, MiniControllerFragment, có thể được thêm vào tệp bố cục ứng dụng của các hoạt động mà bạn muốn hiển thị bộ điều khiển thu nhỏ.
Thêm định nghĩa fragment sau vào cuối cả res/layout/player_activity.xml và res/layout/video_browser.xml:
<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"/>
Nhấp vào nút
Chạy để chạy ứng dụng và truyền video. Khi quá trình phát bắt đầu trên thiết bị nhận, bạn sẽ thấy bộ điều khiển thu nhỏ xuất hiện ở cuối mỗi hoạt động. Bạn có thể điều khiển chế độ phát từ xa bằng bộ điều khiển thu nhỏ. Nếu bạn di chuyển giữa hoạt động duyệt xem và hoạt động trình phát cục bộ, trạng thái của bộ điều khiển thu nhỏ phải luôn đồng bộ với trạng thái phát nội dung nghe nhìn của thiết bị nhận.
8. Thông báo và màn hình khoá
Danh sách kiểm tra thiết kế Google Cast yêu cầu ứng dụng người gửi triển khai các nút điều khiển nội dung nghe nhìn từ một thông báo và màn hình khoá.

Cast SDK cung cấp một MediaNotificationService để giúp ứng dụng người gửi tạo các nút điều khiển nội dung nghe nhìn cho thông báo và màn hình khoá. Dịch vụ này sẽ tự động được gradle hợp nhất vào tệp kê khai của ứng dụng.
MediaNotificationService sẽ chạy ở chế độ nền khi người gửi truyền và sẽ hiện một thông báo có hình thu nhỏ và siêu dữ liệu về mục truyền hiện tại, nút phát/tạm dừng và nút dừng.
Bạn có thể bật thông báo và các chế độ kiểm soát trên màn hình khoá bằng CastOptions khi khởi động CastContext. Theo mặc định, các nút điều khiển nội dung nghe nhìn cho thông báo và màn hình khoá sẽ được bật. Tính năng màn hình khoá sẽ bật miễn là bạn bật thông báo.
Chỉnh sửa CastOptionsProvider và thay đổi phương thức triển khai getCastOptions để khớp với mã này:
import com.google.android.gms.cast.framework.media.CastMediaOptions
import com.google.android.gms.cast.framework.media.NotificationOptions
override fun getCastOptions(context: Context): CastOptions {
val notificationOptions = NotificationOptions.Builder()
.setTargetActivityClassName(VideoBrowserActivity::class.java.name)
.build()
val mediaOptions = CastMediaOptions.Builder()
.setNotificationOptions(notificationOptions)
.build()
return CastOptions.Builder()
.setReceiverApplicationId(context.getString(R.string.app_id))
.setCastMediaOptions(mediaOptions)
.build()
}
Nhấp vào nút
Run (Chạy) để chạy ứng dụng trên thiết bị di động. Truyền một video và chuyển sang ứng dụng mẫu. Sẽ có một thông báo cho video đang phát trên receiver. Khoá thiết bị di động. Lúc này, màn hình khoá sẽ hiển thị các chế độ điều khiển để phát nội dung nghe nhìn trên thiết bị truyền.

9. Lớp phủ giới thiệu
Danh sách kiểm tra thiết kế Google Cast yêu cầu ứng dụng người gửi phải giới thiệu nút Truyền cho người dùng hiện tại để cho họ biết rằng ứng dụng người gửi hiện hỗ trợ truyền và cũng giúp người dùng mới làm quen với Google Cast.

Cast SDK cung cấp một khung hiển thị tuỳ chỉnh, IntroductoryOverlay, có thể dùng để làm nổi bật nút Truyền khi nút này xuất hiện lần đầu tiên với người dùng. Thêm mã sau vào VideoBrowserActivity:
import com.google.android.gms.cast.framework.IntroductoryOverlay
import android.os.Looper
private var mIntroductoryOverlay: IntroductoryOverlay? = null
private fun showIntroductoryOverlay() {
mIntroductoryOverlay?.remove()
if (mediaRouteMenuItem?.isVisible == true) {
Looper.myLooper().run {
mIntroductoryOverlay = com.google.android.gms.cast.framework.IntroductoryOverlay.Builder(
this@VideoBrowserActivity, mediaRouteMenuItem!!)
.setTitleText("Introducing Cast")
.setSingleTime()
.setOnOverlayDismissedListener(
object : IntroductoryOverlay.OnOverlayDismissedListener {
override fun onOverlayDismissed() {
mIntroductoryOverlay = null
}
})
.build()
mIntroductoryOverlay!!.show()
}
}
}
Bây giờ, hãy thêm CastStateListener và gọi phương thức showIntroductoryOverlay khi có thiết bị truyền bằng cách sửa đổi phương thức onCreate và ghi đè phương thức onResume và onPause cho phù hợp với nội dung sau:
import com.google.android.gms.cast.framework.CastState
import com.google.android.gms.cast.framework.CastStateListener
private var mCastStateListener: CastStateListener? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.video_browser)
setupActionBar()
mCastStateListener = object : CastStateListener {
override fun onCastStateChanged(newState: Int) {
if (newState != CastState.NO_DEVICES_AVAILABLE) {
showIntroductoryOverlay()
}
}
}
mCastContext = CastContext.getSharedInstance(this)
}
override fun onResume() {
super.onResume()
mCastContext?.addCastStateListener(mCastStateListener!!)
}
override fun onPause() {
super.onPause()
mCastContext?.removeCastStateListener(mCastStateListener!!)
}
Xoá dữ liệu ứng dụng hoặc xoá ứng dụng khỏi thiết bị. Sau đó, hãy nhấp vào nút
Run (Chạy) để chạy ứng dụng trên thiết bị di động. Bạn sẽ thấy lớp phủ giới thiệu (xóa dữ liệu ứng dụng nếu lớp phủ không hiển thị).
10. 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 truyền phải cung cấp bộ điều khiển mở rộng cho nội dung nghe nhìn đang được truyền. Bộ điều khiển mở rộng là phiên bản toàn màn hình của bộ điều khiển thu nhỏ.

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à 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 có tên là expanded_controller.xml cho bộ điều khiển mở rộng để cung cấp nút Truyền:
<?xml version="1.0" encoding="utf-8"?>
<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 gói expandedcontrols mới trong gói com.google.sample.cast.refplayer. Tiếp theo, hãy tạo một tệp mới có tên là ExpandedControlsActivity.kt trong gói com.google.sample.cast.refplayer.expandedcontrols.
package com.google.sample.cast.refplayer.expandedcontrols
import android.view.Menu
import com.google.android.gms.cast.framework.media.widget.ExpandedControllerActivity
import com.google.sample.cast.refplayer.R
import com.google.android.gms.cast.framework.CastButtonFactory
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
}
}
Bây giờ, hãy khai báo ExpandedControlsActivity trong AndroidManifest.xml trong thẻ application ở trên OPTIONS_PROVIDER_CLASS_NAME:
<application>
...
<activity
android:name="com.google.sample.cast.refplayer.expandedcontrols.ExpandedControlsActivity"
android:label="@string/app_name"
android:launchMode="singleTask"
android:theme="@style/Theme.CastVideosDark"
android:screenOrientation="portrait"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
</intent-filter>
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.google.sample.cast.refplayer.VideoBrowserActivity"/>
</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 ExpandedControlsActivity:
import com.google.sample.cast.refplayer.expandedcontrols.ExpandedControlsActivity
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()
}
Cập nhật phương thức LocalPlayerActivity loadRemoteMedia để hiển thị ExpandedControlsActivity khi nội dung nghe nhìn từ xa được tải:
import com.google.sample.cast.refplayer.expandedcontrols.ExpandedControlsActivity
private fun loadRemoteMedia(position: Int, autoPlay: Boolean) {
if (mCastSession == null) {
return
}
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(buildMediaInfo())
.setAutoplay(autoPlay)
.setCurrentTime(position.toLong()).build())
}
Nhấp vào nút
Chạy để chạy ứng dụng trên thiết bị di động và truyền video. Bạn sẽ thấy bộ điều khiển mở rộng. Chuyển đến danh sách video rồi nhấp vào bộ điều khiển thu nhỏ để tải lại bộ điều khiển mở rộng. Chuyển sang ứng dụng khác để xem thông báo. Nhấp vào hình ảnh thông báo để tải bộ điều khiển mở rộng.
11. Thêm tính năng hỗ trợ Cast Connect
Thư viện Cast Connect cho phép các ứng dụng gửi hiện có giao tiếp với các ứng dụng Android TV thông qua giao thức Cast. Cast Connect được xây dựng dựa trên cơ sở hạ tầng Cast, trong đó ứng dụng cho Android TV của bạn đóng vai trò là một bộ nhận.
Phần phụ thuộc
Lưu ý: Để triển khai Cast Connect, play-services-cast-framework cần là 19.0.0 trở lên.
LaunchOptions
Để chạy ứng dụng Android TV (còn gọi là Trình nhận Android), chúng ta cần đặt cờ setAndroidReceiverCompatible thành true trong đối tượng LaunchOptions. Đối tượng LaunchOptions này quy định cách khởi chạy receiver và được truyền đến CastOptions do lớp CastOptionsProvider trả về. Việc đặt cờ nêu trên thành false sẽ khởi chạy trình nhận web cho mã nhận dạng ứng dụng đã xác định trong Cast Developer Console.
Trong tệp CastOptionsProvider.kt, hãy thêm đoạn mã sau vào phương thức getCastOptions:
import com.google.android.gms.cast.LaunchOptions
...
val launchOptions = LaunchOptions.Builder()
.setAndroidReceiverCompatible(true)
.build()
return new CastOptions.Builder()
.setLaunchOptions(launchOptions)
...
.build()
Đặt thông tin đăng nhập để khởi chạy
Về phía người gửi, bạn có thể chỉ định CredentialsData để biểu thị người đang tham gia phiên. credentials là một chuỗi do người dùng xác định, miễn là ứng dụng ATV của bạn có thể hiểu được chuỗi đó. CredentialsData chỉ được truyền đến ứng dụng cho Android TV của bạn trong thời gian khởi chạy hoặc tham gia. Nếu bạn đặt lại mật khẩu trong khi đang kết nối, mật khẩu đó sẽ không được chuyển đến ứng dụng Android TV.
Để thiết lập Launch Credentials (Thông tin đăng nhập khi khởi chạy), bạn cần xác định và truyền CredentialsData đến đối tượng LaunchOptions. Thêm đoạn mã sau vào phương thức getCastOptions trong tệp CastOptionsProvider.kt:
import com.google.android.gms.cast.CredentialsData
...
val credentialsData = CredentialsData.Builder()
.setCredentials("{\"userId\": \"abc\"}")
.build()
val launchOptions = LaunchOptions.Builder()
...
.setCredentialsData(credentialsData)
.build()
Đặt thông tin đăng nhập trên LoadRequest
Trong trường hợp ứng dụng Web Receiver và ứng dụng cho Android TV của bạn xử lý credentials theo cách khác nhau, bạn có thể cần xác định credentials riêng cho từng ứng dụng. Để xử lý việc này, hãy thêm mã sau vào tệp LocalPlayerActivity.kt trong hàm loadRemoteMedia:
remoteMediaClient.load(MediaLoadRequestData.Builder()
...
.setCredentials("user-credentials")
.setAtvCredentials("atv-user-credentials")
.build())
Tuỳ thuộc vào ứng dụng nhận mà thiết bị gửi đang truyền đến, giờ đây, SDK sẽ tự động xử lý thông tin đăng nhập cần dùng cho phiên hiện tại.
Kiểm thử Cast Connect
Các bước cài đặt APK Android TV trên Chromecast có Google TV
- Tìm địa chỉ IP của thiết bị Android TV. Thông thường, bạn có thể xem thông tin này trong phần Cài đặt > Mạng và Internet > (Tên mạng mà thiết bị của bạn đang kết nối). Ở bên phải, thông tin chi tiết và địa chỉ IP của thiết bị trên mạng sẽ xuất hiện.
- Sử dụng địa chỉ IP của thiết bị để kết nối với thiết bị đó qua ADB bằng thiết bị đầu cuối:
$ adb connect <device_ip_address>:5555
- Trong cửa sổ thiết bị đầu cuối, hãy chuyển đến thư mục cấp cao nhất cho các mẫu của lớp học lập trình mà bạn đã tải xuống khi bắt đầu lớp học lập trình này. Ví dụ:
$ cd Desktop/android_codelab_src
- Cài đặt tệp .apk trong thư mục này vào Android TV bằng cách chạy:
$ adb -s <device_ip_address>:5555 install android-tv-app.apk
- Giờ đây, bạn sẽ thấy một ứng dụng có tên là Truyền video trong trình đơn Ứng dụng của bạn trên thiết bị Android TV.
- Quay lại dự án Android Studio, rồi nhấp vào nút Chạy để cài đặt và chạy ứng dụng người gửi trên thiết bị di động thực. Ở góc trên bên phải, hãy nhấp vào biểu tượng truyền và chọn thiết bị Android TV trong số các lựa chọn hiện có. Lúc này, bạn sẽ thấy ứng dụng cho Android TV khởi chạy trên thiết bị Android TV và khi phát một video, bạn có thể điều khiển chế độ phát video bằng Điều khiển từ xa cho Android TV.
12. Tuỳ chỉnh tiện ích Truyền
Bạn có thể tuỳ chỉnh tiện ích Truyền bằng cách thiết lập màu sắc, tạo kiểu cho các nút, văn bản và giao diện hình thu nhỏ, cũng như chọn các loại nút để hiển thị.
Cập nhật res/values/styles_castvideo.xml
<style name="Theme.CastVideosTheme" parent="Theme.AppCompat.Light.NoActionBar">
...
<item name="mediaRouteTheme">@style/CustomMediaRouterTheme</item>
<item name="castIntroOverlayStyle">@style/CustomCastIntroOverlay</item>
<item name="castMiniControllerStyle">@style/CustomCastMiniController</item>
<item name="castExpandedControllerStyle">@style/CustomCastExpandedController</item>
<item name="castExpandedControllerToolbarStyle">
@style/ThemeOverlay.AppCompat.ActionBar
</item>
...
</style>
Khai báo các giao diện tuỳ chỉnh sau:
<!-- Customize Cast Button -->
<style name="CustomMediaRouterTheme" parent="Theme.MediaRouter">
<item name="mediaRouteButtonStyle">@style/CustomMediaRouteButtonStyle</item>
</style>
<style name="CustomMediaRouteButtonStyle" parent="Widget.MediaRouter.Light.MediaRouteButton">
<item name="mediaRouteButtonTint">#EEFF41</item>
</style>
<!-- Customize Introductory Overlay -->
<style name="CustomCastIntroOverlay" parent="CastIntroOverlay">
<item name="castButtonTextAppearance">@style/TextAppearance.CustomCastIntroOverlay.Button</item>
<item name="castTitleTextAppearance">@style/TextAppearance.CustomCastIntroOverlay.Title</item>
</style>
<style name="TextAppearance.CustomCastIntroOverlay.Button" parent="android:style/TextAppearance">
<item name="android:textColor">#FFFFFF</item>
</style>
<style name="TextAppearance.CustomCastIntroOverlay.Title" parent="android:style/TextAppearance.Large">
<item name="android:textColor">#FFFFFF</item>
</style>
<!-- Customize Mini Controller -->
<style name="CustomCastMiniController" parent="CastMiniController">
<item name="castShowImageThumbnail">true</item>
<item name="castTitleTextAppearance">@style/TextAppearance.AppCompat.Subhead</item>
<item name="castSubtitleTextAppearance">@style/TextAppearance.AppCompat.Caption</item>
<item name="castBackground">@color/accent</item>
<item name="castProgressBarColor">@color/orange</item>
</style>
<!-- Customize Expanded Controller -->
<style name="CustomCastExpandedController" parent="CastExpandedController">
<item name="castButtonColor">#FFFFFF</item>
<item name="castPlayButtonDrawable">@drawable/cast_ic_expanded_controller_play</item>
<item name="castPauseButtonDrawable">@drawable/cast_ic_expanded_controller_pause</item>
<item name="castStopButtonDrawable">@drawable/cast_ic_expanded_controller_stop</item>
</style>
13. Xin chúc mừng
Giờ đây, bạn đã biết cách bật tính năng Truyền cho một ứng dụng video bằng cách sử dụng các tiện ích Cast SDK trên Android.
Để biết thêm thông tin chi tiết, hãy xem hướng dẫn dành cho nhà phát triển Android Sender.