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 Android hiện có để truyền nội dung trên một thiết bị 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 tới TV. Sau đó, người dùng có thể sử 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.
SDK Google Cast cho phép bạn mở rộng ứng dụng của mình để điều khiển TV hoặc hệ thống âm thanh. SDK Cast 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ế của 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ễ 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 trên Android có thể truyền video tới thiết bị hỗ trợ Google Cast.
Kiến thức bạn sẽ học được
- Cách thêm SDK Google Cast vào ứng dụng video mẫu.
- Cách thêm nút Truyền để chọn thiết bị Google Cast.
- Cách kết nối với một Thiết bị truyền và khởi chạy bộ thu nội dung đa phương tiện.
- Cách truyền video.
- Cách thêm bộ điều khiển Cast Mini vào ứng dụng của bạn.
- Cách hỗ trợ thông báo về nội dung nghe nhìn và chế độ điều khiển màn hình khoá.
- Cách thêm tay đ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 Truyền.
- 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 (API cấp 16).
- Cáp dữ liệu USB để kết nối thiết bị di động với máy tính dùng để phát triển.
- Một thiết bị Google Cast như Chromecast hoặc Android TV được định cấu hình có kết nối Internet.
- TV hoặc màn hình có đầu vào HDMI.
- Bạn cần có Chromecast có Google TV để kiểm tra quá trình tích hợp Cast Connect nhưng không bắt buộc trong phần còn lại của lớp học lập trình này. Nếu bạn chưa có dịch vụ hỗ trợ Cast Connect, vui lòng bỏ qua bước Thêm tính năng hỗ trợ Cast Connect ở cuối hướng dẫn này.
Trải nghiệm
- Bạn sẽ cần có kiến thức trước đây về phát triển Kotlin và Android.
- Bạn cũng sẽ cần có kiến thức trướ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á trải nghiệm xây dựng ứng dụng Android của mình như thế nào?
Bạn đánh giá trải nghiệm xem TV của mình ở mức nào?
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 cùng xem ứng dụng mẫu hoàn chỉnh sẽ có giao diện như thế nào. Ứng dụng 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 đó tới thiết bị Google Cast.
Sau khi bạn tải mã nguồn xuống, hướng dẫn sau đây sẽ mô tả cách 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 File > (Tệp >) Mới > Các tuỳ chọn trên trình đơn Import Project... (Nhập dự án).
Chọn thư mục app-done
từ thư mục mã mẫu rồi nhấp vào OK.
Nhấp vào File > (Tệp >) Đồng bộ hoá dự án với tệp Gradle.
Bật tính năng gỡ lỗi qua USB trên thiết bị Android – trên Android 4.2 trở lên, màn hình Tuỳ chọn cho nhà phát triển sẽ bị ẩn theo mặc định. Để hiển thị ứng dụng này, 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 7 lần. Quay lại màn hình trước, chuyển đến Hệ thống > Nâng cao rồi nhấn vào Tuỳ 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.
Cắm thiết bị Android rồi nhấp vào nút Run (Chạy) trong Android Studio. Bạn sẽ thấy ứng dụng video có tên Truyền video xuất hiện sau vài giây.
Nhấp vào nút Truyền trong ứng dụng video và chọn thiết bị Google Cast của bạn.
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 của bạn.
Bộ điều khiển mở rộng sẽ hiển thị. Bạn có thể sử 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.
Lúc này, bạn sẽ thấy một tay điều khiển mini ở cuối màn hình.
Nhấp vào nút tạm dừng trên bộ điều khiển nhỏ để tạm dừng video trên bộ thu. Nhấp vào nút phát trên tay điều khiển mini để tiếp tục phát lại video.
Nhấp vào nút trang chủ của thiết bị di động. Kéo thông báo xuống và giờ đây bạn sẽ thấy thông báo cho phiên Truyền.
Khoá điện thoại và khi mở khoá, bạn sẽ thấy thông báo trên màn hình khoá điều khiển việc phát nội dung nghe nhìn hoặc ngừng truyền.
Quay lại ứng dụng video và nhấp vào nút Truyền để dừng truyền trên thiết bị Google Cast.
Câu hỏi thường gặp
4. Chuẩn bị dự án bắt đầu
Chúng tôi cần thêm tính năng hỗ trợ cho Google Cast vào ứng dụng khởi động mà bạn đã tải xuống. Dưới đây là một số thuật ngữ của Google Cast mà chúng ta sẽ sử dụng trong lớp học lập trình này:
- một ứng dụng của người gửi chạy trên thiết bị di động hoặc máy tính xách tay
- một ứng dụng receiver (trình thu nhận) chạy trên thiết bị 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-start
trong tệp mã mẫu tải xuống (Chọn Import Project (Nhập dự án) trên màn hình chào mừng hoặc chọn tuỳ chọn File > New > Import Project... (Tệp > Mới > Nhập dự án...)). - Nhấp vào nút Sync Project with Gradle Files (Đồ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áy chủ web từ xa và cung cấp danh sách để người dùng duyệt qua. Người dùng có thể chọn một video để xem chi tiết hoặc phát video đó trên thiết bị di động.
Ứng dụng này bao gồm hai hoạt động chính: VideoBrowserActivity
và LocalPlayerActivity
. Để tích hợp chức năng của Google Cast, Hoạt động cần phải 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 sẽ cần thêm MediaRouteButton
(được cung cấp trong thư viện hỗ trợ MediaRouter) làm MediaRouteActionProvider
và điều này chỉ có tác dụng nếu hoạt động 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 được yêu cầu.
VideoBrowserActivity
Hoạt động này chứa một Fragment
(VideoBrowserFragment
). Danh sách này được ArrayAdapter
(VideoListAdapter
hỗ trợ). Danh sách video và siêu dữ liệu liên quan được lưu trữ trên máy chủ từ xa dưới dạng tệp JSON. AsyncTaskLoader
(VideoItemLoader
) tìm nạp JSON này và xử lý để tạo danh sách đối tượng MediaItem
.
Đối tượng MediaItem
mô hình hoá một video và siêu dữ liệu liên kết với video đó, chẳng hạn như tiêu đề, nội dung mô tả, URL của sự kiện phát trực tiếp, URL của hình ảnh hỗ trợ và Đoạn văn bản được liên kết (cho phụ đề) (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 đó thành Bundle
và ngược lại.
Khi tạo danh sách MediaItems
, trình tải sẽ chuyển danh sách đó đến VideoListAdapter
, sau đó hiển thị danh sách MediaItems
trong VideoBrowserFragment
. Người dùng nhìn thấy một danh sách hình thu nhỏ video kèm nội dung mô tả ngắn cho từng video. Khi bạn chọn một mục, MediaItem
tương ứng sẽ được chuyển đổi thành Bundle
và được truyền cho 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 lưu trữ một VideoView
, một số thành phần đ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 che phần trên cùng của màn hình, chừa chỗ cho nội dung 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 trên thiết bị.
Phần phụ thuộc
Vì chúng ta đang dùng AppCompatActivity
nên cần có thư viện hỗ trợ AppCompat. Để quản lý danh sách video và lấy hình ảnh một cách không đồng bộ cho danh sách, chúng ta đang 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 hiển thị nút Truyền trong mỗi hoạt động của ứng dụng đó. Nhấp vào nút Truyền sẽ hiển thị danh sách Thiết bị truyền mà người dùng có thể chọn. Nếu người dùng đang phát nội dung cục bộ trên thiết bị truyền, 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 tới Thiết bị truyền. Người dùng phải có khả năng kết nối hoặc ngắt kết nối khỏ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ư được 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 cần thiết của thư viện:
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 rằng bản dựng dự án không gặp lỗi.
Khởi chạy
Khung Cast có một đối tượng singleton toàn cầu là CastContext
, có chức năng điều phối tất cả các 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 cho việc khởi chạy singleton CastContext
. Lựa chọn quan trọng nhất là ID ứng dụng nhận. Mã này được dùng để lọc kết quả khám phá thiết bị Truyền và để khởi chạy ứng dụng nhận khi phiên Truyền bắt đầu.
Khi phát triển ứng dụng hỗ trợ Cast của riêng mình, bạn phải đăng ký làm nhà phát triển Cast và sau đó lấy ID ứng dụng cho ứng dụng của mình. Đối với lớp học lập trình này, chúng ta sẽ sử dụng 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 "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 từng phần CastContext
trong phương thức onCreate VideoBrowserActivity
:
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 động vào LocalPlayerActivity
.
Nút truyền
Giờ đây, 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 thiết bị Truyền. Nút Truyền được MediaRouteButton
trong thư viện hỗ trợ MediaRouter triển khai. Giống như mọi biểu tượng hành động 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.
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 Truyền:
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 của bạn. Bạn sẽ thấy một nút Truyền trong thanh tác vụ của ứng dụng và khi bạn nhấp vào nút đó, nút Truyền sẽ liệt kê các thiết bị Truyền trên mạng cục bộ của bạn. Tính năng khám phá thiết bị được CastContext
quản lý tự động. Chọn Thiết bị truyền của bạn và ứng dụng bộ 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 web và hoạt động của trình phát trên máy. Trạng thái của nút Truyền luôn được đồng bộ hoá.
Chúng tôi chưa kết nối bất kỳ tính năng hỗ trợ nào cho tính năng 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. Đang truyền nội dung video
Chúng tôi sẽ mở rộng ứng dụng mẫu để có thể 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 các sự kiện khác nhau do khung Truyền tạo.
Đang 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 thiết bị truyền, bạn cần làm những việc sau:
- Tạo đối tượng
MediaInfo
để mô hình hoá một mục nội dung đa phương tiệ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
MediaInfo
vào trình thu 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 người nhận dựa trên tương tác của người dùng.
Chúng ta đã thực hiện Bước 2 ở phần trước. Bước 3 là dễ thực hiện bằng khung Truyền. Bước 1 tương tự như ánh xạ một đối tượng với đối tượng khác; MediaInfo
là định dạng mà khung Cast hiểu được và MediaItem
là gói ứng dụng của chúng ta đóng gói cho một mục nội dung đa phương tiện; chúng ta có thể dễ dàng liên kết MediaItem
với MediaInfo
.
Ứng dụng mẫu LocalPlayerActivity
đã phân biệt giữa chế độ phát cục bộ và 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 của trình phát mẫu. Điều quan trọng là phải hiểu rằng trình phát đa phương tiện của ứng dụng sẽ phải được sửa đổi để nhận biết hai vị trí phát lại 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ì 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 quá trình chuyển đổi trạng thái xảy ra trong khung Truyền. Ví dụ: nếu bắt đầu truyền, chúng ta cần dừng quá trình phát trên thiết bị và tắt một số nút điều khiển. Tương tự như vậy, nếu ngừng truyền khi đang ở trong hoạt động này, chúng ta cần phải chuyển sang chế độ phát trên thiết bị. Để xử lý vấn đề đó, chúng ta cần nghe các sự kiện khác nhau do khung Truyền tạo.
Quản lý phiên truyền
Đối với khung Truyền, phiên Truyền sẽ 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 tin nhắn từ trình phát nội dung nghe nhìn của người nhận.
Phiên Truyền sẽ tự động bắt đầu khi người dùng chọn một thiết bị trong nút Truyền và sẽ tự động dừng khi người dùng ngắt kết nối. SDK Truyền cũng tự động xử lý việc kết nối lại với một phiên của bộ thu do sự cố kết nối 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 ta muốn biết khi nào chúng ta kết nối hoặc bị ngắt kết nối khỏi Thiết bị truyền để có thể chuyển sang hoặc từ trình phát cục bộ. Lưu ý rằng kết nối có thể bị gián đoạn không chỉ do phiên bản ứng dụng đang chạy trên thiết bị di động mà còn do phiên bản khác của ứng dụng (hoặc ứng dụng khác) chạy trên thiết bị di động khác.
Phiên hiện đang hoạt động có thể truy cập được bằng tài khoản SessionManager.getCurrentSession()
. Phiên được tạo và xé tự động để phản hồi 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 chạy một số biến mà chúng ta sẽ sử dụng trong hoạt động. 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 bộ API tiện lợi để quản lý việc phát nội dung nghe nhìn từ xa trên bộ thu. Đối với CastSession
có hỗ trợ phát nội dung nghe nhìn, SDK sẽ tự động tạo một thực thể của RemoteMediaClient
. Bạn có thể truy cập đố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ộ thu:
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()
}
}
Bây giờ, hãy cập nhật các phương thức hiện có để sử dụng logic của phiên Truyền nhằm hỗ trợ 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 Run (Chạy) để chạy ứng dụng trên thiết bị di động của bạn. Kết nối với Thiết bị truyền của bạn và bắt đầu phát video. Bạn sẽ thấy video đang phát trên đầu thu.
7. Tay đ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 Truyền đều cung cấp bộ điều khiển nhỏ xuất hiện khi người dùng di chuyển khỏi trang nội dung hiện tại. Bộ điều khiển mini cho phép truy cập tức thì và hiển thị lời nhắc cho phiên Truyền hiện tại.
SDK Truyền cung cấp 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 trình điều khiển thu nhỏ.
Thêm định nghĩa mảnh sau đây 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 Run (Chạy) để chạy ứng dụng và truyền video. Khi quá trình phát bắt đầu trên receiver, bạn sẽ thấy trình điều khiển mini xuất hiện ở cuối từng hoạt động. Bạn có thể điều khiển chế độ phát từ xa bằng tay điều khiển nhỏ. Nếu bạn di chuyển giữa hoạt động duyệt web và hoạt động trên trình phát trên máy, thì trạng thái của tay đ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 bộ nhận.
8. Thông báo và màn hình khoá
Danh sách kiểm tra thiết kế của Google Cast yêu cầu ứng dụng gửi để triển khai các tính năng điều khiển nội dung nghe nhìn từ thông báo và màn hình khóa.
SDK Truyền cung cấp một MediaNotificationService
để giúp ứng dụng gửi xây dựng các chế độ điều khiển nội dung nghe nhìn cho thông báo và màn hình khoá. Dịch vụ sẽ tự động được hợp nhất vào tệp kê khai của ứng dụng qua gradle.
MediaNotificationService
sẽ chạy ở chế độ nền khi người gửi đang truyền và sẽ hiển thị thông báo kèm theo hình thu nhỏ của hình ảnh và siêu dữ liệu về mục đang truyền, nút phát/tạm dừng và nút dừng.
Bạn có thể bật các tính năng điều khiển thông báo và màn hình khoá bằng CastOptions
khi khởi tạo CastContext
. Các chế độ điều khiển nội dung nghe nhìn cho thông báo và màn hình khoá được bật theo mặc định. Tính năng màn hình khoá sẽ bật khi bạn bật thông báo.
Chỉnh sửa CastOptionsProvider
và thay đổi cách 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 của bạn. Truyền một video và rời khỏi ứng dụng mẫu. Sẽ có một thông báo cho video hiện đang phát trên bộ thu. Khoá thiết bị di động của bạn và màn hình khoá giờ đây sẽ hiển thị các nút điều khiển chế độ 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ế của Google Cast yêu cầu một ứng dụng dành cho người gử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 gửi hiện hỗ trợ tính năng truyền và cũng giúp người dùng mới làm quen với Google Cast.
SDK Truyền cung cấp 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 hiển thị 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 một 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
rồi ghi đè phương thức onResume
và onPause
sao cho khớp với các phương thức 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ị của bạn. Sau đó, nhấp vào nút Run (Chạy) để chạy ứng dụng trên thiết bị di động và bạn sẽ thấy lớp phủ giới thiệu (xoá dữ liệu ứng dụng nếu lớp phủ không hiển thị).
10. Đã mở rộng bộ điều khiển
Danh sách kiểm tra thiết kế của Google Cast yêu cầu ứng dụng gửi yêu cầu cung cấp bộ điều khiển mở rộng cho nội dung đa phương tiệ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 tay điều khiển mini.
SDK Truyền cung cấp một tiện ích cho bộ điều khiển mở rộng có tên là ExpandedControllerActivity
. Đây là lớp trừu tượng mà bạn phải tạo lớp con để thêm nút Truyền.
Trước tiên, hãy tạo một tệp tài nguyên trình đơn mới có tên là expanded_controller.xml
để trình đ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 mới expandedcontrols
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
phía 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
cũng như thay đổi NotificationOptions
và 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 Run (Chạy) để chạy ứng dụng trên thiết bị di động của bạn và truyền một video. Bạn sẽ thấy bộ điều khiển mở rộng. Quay lại danh sách video. Khi bạn nhấp vào trình điều khiển thu nhỏ, bộ điều khiển mở rộng sẽ được tải lại. Di chuyển ra khỏi ứng dụng để 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 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 Android TV của bạn hoạt động như một bộ thu.
Phần phụ thuộc
Lưu ý: Để triển khai Cast Connect, play-services-cast-framework
cần phải từ 19.0.0
trở lên.
LaunchOptions
Để chạy ứng dụng Android TV, còn được gọi là Android receiver, chúng ta cần đặt cờ setAndroidReceiverCompatible
thành true trong đối tượng LaunchOptions
. Đối tượng LaunchOptions
này cho biết cách chạy trình thu nhận và 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 thu nhận web cho ID ứ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 xác thực chạy
Ở phía người gửi, bạn có thể chỉ định CredentialsData
để đại diện cho người tham gia phiên. credentials
là một chuỗi mà người dùng có thể xác định, miễn là ứng dụng ATV của bạn có thể hiểu được chuỗi đó. CredentialsData
chỉ được chuyển đến ứng dụng Android TV của bạn trong thời gian chạy hoặc tham gia. Nếu bạn thiết lập lại thiết bị trong khi kết nối, thì thiết bị sẽ không được chuyển đến ứng dụng Android TV.
Để đặt Thông tin xác thực chạy CredentialsData
cần được xác định và truyền vào đối tượng LaunchOptions
. Thêm 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 xác thực trên LoadRequest
Trong trường hợp ứng dụng Trình thu phát trên web và ứng dụng Android TV xử lý credentials
theo cách khác nhau, bạn có thể cần phải xác định credentials
riêng cho mỗi ứng dụng. Để xử lý vấn đề 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à người gửi đang truyền tới, SDK giờ đây sẽ tự động xử lý thông tin xác thực cần sử 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, công cụ này có 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). Ở phía bên phải, ứng dụng sẽ hiển thị thông tin chi tiết và IP của thiết bị trên mạng.
- Sử dụng địa chỉ IP cho thiết bị của bạn để 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
- Từ cửa sổ dòng lệnh, hãy chuyển đến thư mục cấp cao nhất của các mẫu trong lớp học lập trình mà bạn đã tải xuống ở đầ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 có thể 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 của bạn và nhấp vào nút Run (Chạy) để cài đặt & chạy ứng dụng gửi trên thiết bị di động thực của bạn. Ở góc trên bên phải, hãy nhấp vào biểu tượng truyền rồi chọn thiết bị Android TV của bạn trong số các lựa chọn có sẵn. Bây giờ, bạn sẽ thấy ứng dụng Android TV khởi chạy trên thiết bị Android TV của mình và quá trình phát video sẽ cho phép bạn điều khiển quá trình phát lại 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 đặt màu, tạo kiểu cho nút, văn bản và hình thu nhỏ cũng như bằng cách chọn loại nút cần 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 ứng dụng video bằng cách sử dụng tiện ích SDK Truyền trên Android.
Để biết thêm chi tiết, hãy xem hướng dẫn dành cho nhà phát triển Trình gửi Android.