1. 總覽

本程式碼研究室將教您如何修改現有的 Android 影片應用程式,在支援 Google Cast 的裝置上投放內容。
什麼是 Google Cast?
使用者可以透過 Google Cast,將行動裝置中的內容投放到電視上。使用者就能將行動裝置當成遙控器,控制電視上的媒體播放。
透過 Google Cast SDK,您可以擴充應用程式,控制電視或音響系統。您可以根據 Google Cast 設計檢查清單,使用 Cast SDK 新增必要的使用者介面元件。
我們提供 Google Cast 設計檢查清單,確保所有支援的平台都能提供簡單易懂的 Cast 使用者體驗。
我們要建構什麼內容?
完成本程式碼研究室後,您將擁有一個 Android 影片應用程式,可將影片投放到支援 Google Cast 的裝置。
課程內容
- 如何將 Google Cast SDK 新增至範例影片應用程式。
- 如何新增 Cast 按鈕,選取 Google Cast 裝置。
- 如何連線至 Cast 裝置並啟動媒體接收器。
- 如何投放影片。
- 如何在應用程式中加入 Cast 迷你遙控器。
- 如何支援媒體通知和螢幕鎖定畫面控制項。
- 如何新增展開的控制器。
- 如何提供簡介疊加層。
- 如何自訂 Google Cast 小工具。
- 如何與 Cast Connect 整合
軟硬體需求
- 最新版 Android SDK。
- Android Studio 3.2 以上版本
- 搭載 Android 4.1 以上版本 (Jelly Bean,API 級別 16) 的行動裝置。
- USB 資料傳輸線,用於將行動裝置連接至開發電腦。
- 已設定網路連線的 Google Cast 裝置,例如 Chromecast 或 Android TV。
- 具備 HDMI 輸入端的電視或螢幕。
- 如要測試 Cast Connect 整合,必須使用 Chromecast (支援 Google TV),但其餘程式碼研究室內容則不一定需要。如果沒有,請略過本教學課程結尾的「新增 Cast Connect 支援」步驟。
體驗
- 須具備 Kotlin 和 Android 開發知識。
- 你還需要具備觀看電視的相關知識 :)
您會如何使用本教學課程?
您對建構 Android 應用程式的體驗滿意嗎?
你對觀看電視的體驗滿意嗎?
2. 取得程式碼範例
您可以將所有範例程式碼下載到電腦...
並解壓縮下載的 ZIP 檔案。
3. 執行範例應用程式

首先,我們來看看完成的範例應用程式。這個應用程式是基本影片播放器。使用者可以從清單中選取影片,然後在裝置上播放影片,或將影片投放到 Google Cast 裝置。
下載程式碼後,請按照下列指示在 Android Studio 中開啟並執行已完成的範例應用程式:
在歡迎畫面中選取「Import Project」,或依序選取「File」>「New」>「Import Project...」選單選項。
從範例程式碼資料夾選取 
app-done 目錄,然後按一下「確定」。
依序點選「File」>
「Sync Project with Gradle Files」。
在 Android 裝置上啟用 USB 偵錯功能。在 Android 4.2 以上版本中,「開發人員選項」畫面會預設為隱藏。如要將其顯示出來,請依序前往「設定」>「關於手機」,然後輕觸「版本號碼」七次。返回上一個畫面,依序前往「系統」>「進階」,然後輕觸底部的「開發人員選項」,再輕觸「USB 偵錯」即可開啟這項功能。
插入 Android 裝置,然後按一下 Android Studio 中的「Run」
按鈕。幾秒後,你應該會看到名為「投放影片」的影片應用程式。
按一下影片應用程式中的「投放」按鈕,然後選取 Google Cast 裝置。
選取影片,然後按一下播放按鈕。
影片就會開始在 Google Cast 裝置上播放。
畫面會顯示展開的控制器。你可以使用播放/暫停按鈕控制播放。
返回影片清單。
畫面底部會顯示迷你控制器。
按一下迷你遙控器中的暫停按鈕,即可暫停接收器上的影片。按一下迷你控制器中的「播放」按鈕,即可繼續播放影片。
按一下行動裝置的主畫面按鈕。向下拉動通知,現在應該會看到 Cast 工作階段的通知。
鎖定手機,解鎖後螢幕鎖定畫面會顯示通知,方便你控制媒體播放或停止投放。
返回影片應用程式,然後按一下「投放」按鈕,停止在 Google Cast 裝置上投放內容。
常見問題
4. 準備啟動專案

我們需要為您下載的啟動應用程式新增 Google Cast 支援。以下是本程式碼研究室會用到的一些 Google Cast 術語:
- 傳送者應用程式在行動裝置或筆電上執行,
- Google Cast 裝置上執行接收端應用程式。
現在您可以使用 Android Studio,以入門專案為基礎進行建構:
- 從下載的範例程式碼中選取

app-start目錄 (在歡迎畫面中選取「Import Project」,或依序選取「File」>「New」>「Import Project...」選單選項)。 - 按一下「Sync Project with Gradle Files」
按鈕。 - 按一下「Run」
按鈕,執行應用程式並探索 UI。
應用程式設計
應用程式會從遠端網路伺服器擷取影片清單,並提供清單供使用者瀏覽。使用者可以選取影片查看詳細資料,或在行動裝置上播放影片。
這個應用程式包含兩個主要活動:VideoBrowserActivity 和 LocalPlayerActivity。如要整合 Google Cast 功能,Activity 必須繼承 AppCompatActivity 或其父項 FragmentActivity。由於我們需要將 MediaRouteButton (在 MediaRouter 支援程式庫中提供) 新增為 MediaRouteActionProvider,因此只有在活動從上述類別繼承時,這項限制才會存在。MediaRouter 支援程式庫依附於 AppCompat 支援程式庫,後者提供必要類別。
VideoBrowserActivity
這項活動包含 Fragment (VideoBrowserFragment)。這份清單由 ArrayAdapter (VideoListAdapter) 提供支援。影片清單和相關聯的中繼資料會以 JSON 檔案的形式,儲存在遠端伺服器上。AsyncTaskLoader (VideoItemLoader) 會擷取這個 JSON,並進行處理來建構 MediaItem 物件清單。
MediaItem 物件會建立影片及其相關中繼資料的模型,例如影片標題、說明、串流網址、支援圖片的網址,以及相關聯的文字軌 (如有,用於隱藏式輔助字幕)。MediaItem 物件會在活動之間傳遞,因此 MediaItem 具有實用方法,可將其轉換為 Bundle,反之亦然。
載入器建構 MediaItems 清單時,會將該清單傳遞至 VideoListAdapter,然後 VideoListAdapter 會在 VideoBrowserFragment 中呈現 MediaItems 清單。系統會向使用者顯示影片縮圖清單,並附上每部影片的簡短說明。選取項目後,對應的 MediaItem 會轉換為 Bundle,並傳遞至 LocalPlayerActivity。
LocalPlayerActivity
這項活動會顯示特定影片的中繼資料,並允許使用者在行動裝置上播放影片。
活動會顯示 VideoView、部分媒體控制項,以及顯示所選影片說明的文字區域。播放器會遮住畫面頂端,下方則會顯示影片的詳細說明。使用者可以播放/暫停或跳轉影片的本機播放內容。
依附元件
由於我們使用 AppCompatActivity,因此需要 AppCompat 支援程式庫。我們使用 Volley 程式庫管理影片清單,並以非同步方式取得清單的圖片。
常見問題
5. 新增「投放」按鈕

支援 Cast 的應用程式會在每個活動中顯示「投放」按鈕。按一下「投放」按鈕,即可選取要使用的 Cast 裝置。如果使用者在傳送端裝置上播放本機內容,選取 Cast 裝置後,系統就會在該裝置上開始或繼續播放內容。在 Cast 工作階段期間,使用者隨時可以按一下「投放」按鈕,停止將應用程式投放到 Cast 裝置。如 Google Cast 設計檢查清單所述,使用者必須能在應用程式的任何活動中連線或中斷與 Cast 裝置的連線。
依附元件
更新應用程式的 build.gradle 檔案,加入必要的程式庫依附元件:
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"
}
同步處理專案,確認專案建構時不會發生錯誤。
初始化
Cast 架構有一個全域單例模式物件 CastContext,可協調所有 Cast 互動。
您必須實作 OptionsProvider 介面,才能提供初始化 CastContext 單例項所需的 CastOptions。最重要的選項是接收器應用程式 ID,用於篩選 Cast 裝置探索結果,並在啟動 Cast 工作階段時啟動接收器應用程式。
開發支援 Cast 的應用程式時,您必須先註冊成為 Cast 開發人員,然後取得應用程式的應用程式 ID。在本程式碼研究室中,我們將使用範例應用程式 ID。
將下列新的 CastOptionsProvider.kt 檔案新增至專案的 com.google.sample.cast.refplayer 套件:
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
}
}
現在,在應用程式 AndroidManifest.xml 檔案的「application」標記中宣告 OptionsProvider:
<meta-data
android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
android:value="com.google.sample.cast.refplayer.CastOptionsProvider" />
在 VideoBrowserActivity onCreate 方法中延遲初始化 CastContext:
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)
}
將相同的初始化邏輯新增至 LocalPlayerActivity。
投放按鈕
CastContext 初始化完成後,我們需要新增 Cast 按鈕,讓使用者選取 Cast 裝置。投放按鈕是透過 MediaRouter 支援程式庫的 MediaRouteButton 實作。如同可新增至活動的任何動作圖示 (使用 ActionBar 或 Toolbar),您必須先將對應的選單項目新增至選單。
編輯 res/menu/browse.xml 檔案,並在設定項目之前,於選單中新增 MediaRouteActionProvider 項目:
<item
android:id="@+id/media_route_menu_item"
android:title="@string/media_route_menu_title"
app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
app:showAsAction="always"/>
使用 CastButtonFactory 將 MediaRouteButton 連接至 Cast 架構,藉此覆寫 VideoBrowserActivity 的 onCreateOptionsMenu() 方法:
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
}
以類似方式覆寫 LocalPlayerActivity 中的 onCreateOptionsMenu。
按一下「Run」
按鈕,在行動裝置上執行應用程式。應用程式的動作列中應該會顯示「投放」按鈕,點按後會列出區域網路中的 Cast 裝置。裝置探索功能由 CastContext 自動管理。選取 Cast 裝置,範例接收器應用程式就會載入至 Cast 裝置。您可以在瀏覽活動和本機播放器活動之間切換,且「投放」按鈕狀態會保持同步。
我們尚未支援媒體播放功能,因此你還無法在 Cast 裝置上播放影片。按一下「投放」按鈕即可中斷連線。
6. 投放影片內容

我們會擴充範例應用程式,以便在 Cast 裝置上遠端播放影片。為此,我們需要監聽 Cast 架構產生的各種事件。
投放媒體
如要在 Cast 裝置上播放媒體,大致上需要執行下列操作:
- 建立
MediaInfo物件,模擬媒體項目。 - 連線至 Cast 裝置,然後啟動接收器應用程式。
- 將
MediaInfo物件載入接收器,然後播放內容。 - 追蹤媒體狀態。
- 根據使用者互動,將播放指令傳送至接收器。
我們已在上一個章節中完成步驟 2。使用 Cast 架構即可輕鬆完成步驟 3。步驟 1 是將一個物件對應至另一個物件;MediaInfo 是 Cast 架構可理解的項目,而 MediaItem 是應用程式的媒體項目封裝,我們可以輕鬆將 MediaItem 對應至 MediaInfo。
範例應用程式 LocalPlayerActivity 已使用這個列舉,區分本機和遠端播放:
private var mLocation: PlaybackLocation? = null
enum class PlaybackLocation {
LOCAL, REMOTE
}
enum class PlaybackState {
PLAYING, PAUSED, BUFFERING, IDLE
}
在本程式碼研究室中,您不必瞭解所有範例播放器邏輯的確切運作方式。請務必瞭解,您必須修改應用程式的媒體播放器,才能以類似的方式辨識這兩個播放位置。
目前本機播放器一律處於本機播放狀態,因為播放器還不瞭解投放狀態。我們需要根據 Cast 架構中發生的狀態轉換更新 UI。舉例來說,如果開始投放,我們需要停止本機播放並停用部分控制項。同樣地,如果我們在這個活動中停止投放,就需要轉換為本機播放。如要處理這類情況,我們需要監聽 Cast 架構產生的各種事件。
管理 Cast 工作階段
在 Cast 架構中,Cast 工作階段會結合連線至裝置、啟動 (或加入)、連線至接收器應用程式,以及視需要初始化媒體控制項管道等步驟。媒體控制管道是 Cast 架構用來傳送及接收接收器媒體播放器訊息的方式。
使用者選取 Cast 按鈕中的裝置後,系統就會自動啟動 Cast 工作階段,並在使用者中斷連線時自動停止。如果發生網路問題,Cast SDK 也會自動重新連線至接收器工作階段。
讓我們在 LocalPlayerActivity 中新增 SessionManagerListener:
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()
}
}
}
在 LocalPlayerActivity 活動中,我們希望在連線或中斷與 Cast 裝置的連線時收到通知,以便切換至本機播放器或從本機播放器切換。請注意,不只是在行動裝置上執行的應用程式執行個體會中斷連線,在其他行動裝置上執行的應用程式 (或另一個應用程式) 執行個體也可能中斷連線。
目前有效的工作階段可透過 SessionManager.getCurrentSession() 存取。系統會因應使用者與 Cast 對話方塊的互動,自動建立及終止工作階段。
我們需要註冊工作階段監聽器,並初始化活動中會用到的一些變數。將 LocalPlayerActivity onCreate 方法變更為:
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)
}
}
...
}
正在載入媒體
在 Cast SDK 中,RemoteMediaClient 提供一組便利的 API,可管理接收端上的遠端媒體播放作業。對於支援媒體播放的 CastSession,SDK 會自動建立 RemoteMediaClient 的執行個體。只要在 CastSession 例項上呼叫 getRemoteMediaClient() 方法,即可存取。將下列方法新增至 LocalPlayerActivity,在接收器上載入目前選取的影片:
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()
}
}
現在請更新各種現有方法,使用 Cast 工作階段邏輯支援遠端播放:
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()
}
針對 updatePlayButton 方法,變更 isConnected 變數的值:
private fun updatePlayButton(state: PlaybackState?) {
...
val isConnected = (mCastSession != null
&& (mCastSession!!.isConnected || mCastSession!!.isConnecting))
...
}
現在,按一下「Run」
按鈕,即可在行動裝置上執行應用程式。連線至 Google Cast 裝置,然後開始播放影片。接收裝置上應該會開始播放影片。
7. 迷你遙控器
根據 Cast 設計檢查清單規定,所有 Cast 應用程式都必須提供迷你控制器,使用者離開目前的內容頁面時,系統就會顯示迷你控制器。迷你控制器可讓您即時存取目前的 Cast 工作階段,並顯示提醒。

Cast SDK 提供自訂檢視區塊 MiniControllerFragment,可新增至您想顯示迷你遙控器的活動應用程式版面配置檔案。
在 res/layout/player_activity.xml 和 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"/>
按一下「Run」
按鈕,即可執行應用程式並投放影片。在接收裝置上開始播放內容時,每個活動底部都會顯示迷你遙控器。你可以使用迷你控制器控制遠端播放。在瀏覽活動和本機播放器活動之間切換時,迷你遙控器狀態應與接收器媒體播放狀態保持同步。
8. 通知和螢幕鎖定
Google Cast 設計檢查清單規定,傳送端應用程式必須從通知和螢幕鎖定畫面導入媒體控制項。

Cast SDK 提供 MediaNotificationService,協助傳送端應用程式為通知和螢幕鎖定畫面建構媒體控制項。Gradle 會自動將服務合併至應用程式資訊清單。
當傳送者投放內容時,MediaNotificationService 會在背景執行,並顯示通知,內含目前投放項目的圖片縮圖和中繼資料、播放/暫停按鈕和停止按鈕。
初始化 CastContext 時,可以使用 CastOptions 啟用通知和螢幕鎖定控制項。通知和螢幕鎖定畫面的媒體控制項預設為開啟。只要開啟通知功能,螢幕鎖定功能就會一併啟用。
編輯 CastOptionsProvider,並將 getCastOptions 實作項目變更為符合下列程式碼:
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()
}
按一下「Run」
按鈕,在行動裝置上執行應用程式。投放影片並離開範例應用程式。接收器應會顯示目前播放影片的通知。鎖定行動裝置,螢幕鎖定畫面現在應該會顯示 Cast 裝置的媒體播放控制項。

9. 入門重疊廣告
Google Cast 設計檢查清單要求傳送端應用程式向現有使用者介紹 Cast 按鈕,讓他們瞭解傳送端應用程式現在支援 Cast,同時協助不熟悉 Google Cast 的使用者。

Cast SDK 提供自訂檢視區塊 IntroductoryOverlay,可在首次向使用者顯示 Cast 按鈕時,醒目顯示該按鈕。在 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()
}
}
}
現在,請新增 CastStateListener,並在有 Cast 裝置可用時呼叫 showIntroductoryOverlay 方法,方法是修改 onCreate 方法,並覆寫 onResume 和 onPause 方法,使其符合下列條件:
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!!)
}
清除應用程式資料或從裝置中移除應用程式。接著,按一下「執行」
按鈕,在行動裝置上執行應用程式,您應該會看到簡介疊加層 (如果沒有顯示疊加層,請清除應用程式資料)。
10. 擴充控制器
Google Cast 設計檢查清單規定,傳送器應用程式必須為投放的媒體提供擴充控制器。展開的控制器是迷你控制器的全螢幕版本。

Cast SDK 提供名為 ExpandedControllerActivity 的擴充控制器小工具。這是抽象類別,您必須建立子類別才能新增 Cast 按鈕。
首先,請為擴充控制器建立新的選單資源檔案 (名為 expanded_controller.xml),提供 Cast 按鈕:
<?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>
在 com.google.sample.cast.refplayer 套件中建立新套件 expandedcontrols。接著,在 com.google.sample.cast.refplayer.expandedcontrols 套件中建立名為 ExpandedControlsActivity.kt 的新檔案。
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
}
}
現在,請在 OPTIONS_PROVIDER_CLASS_NAME 上方的 application 標記中,宣告 AndroidManifest.xml 內的 ExpandedControlsActivity:
<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>
編輯 CastOptionsProvider,然後變更 NotificationOptions 和 CastMediaOptions,將目標活動設為 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()
}
更新 LocalPlayerActivity loadRemoteMedia 方法,在載入遠端媒體時顯示 ExpandedControlsActivity:
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())
}
按一下「Run」
按鈕,在行動裝置上執行應用程式並投放影片。您應該會看到展開的控制器。返回影片清單,然後點選迷你控制器,系統就會再次載入展開的控制器。離開應用程式即可查看通知。按一下通知圖片,即可載入展開的控制器。
11. 新增 Cast Connect 支援
現有的發送端應用程式可透過 Cast Connect 程式庫,使用 Cast 協定與 Android TV 應用程式通訊。Cast Connect 以 Cast 基礎架構為基礎,Android TV 應用程式則做為接收器。
依附元件
注意:如要實作 Cast Connect,play-services-cast-framework 必須為 19.0.0 以上版本。
LaunchOptions
如要啟動 Android TV 應用程式 (又稱 Android 接收器),我們需要在 LaunchOptions 物件中將 setAndroidReceiverCompatible 標記設為 true。這個 LaunchOptions 物件會決定接收器啟動方式,並傳遞至 CastOptionsProvider 類別傳回的 CastOptions。將上述旗標設為 false,系統就會在 Cast 開發人員控制台中,啟動已定義應用程式 ID 的網頁接收器。
在 CastOptionsProvider.kt 檔案中,將下列內容新增至 getCastOptions 方法:
import com.google.android.gms.cast.LaunchOptions
...
val launchOptions = LaunchOptions.Builder()
.setAndroidReceiverCompatible(true)
.build()
return new CastOptions.Builder()
.setLaunchOptions(launchOptions)
...
.build()
設定啟動憑證
在傳送端,您可以指定 CredentialsData 代表加入工作階段的使用者。credentials 是使用者可定義的字串,只要 ATV 應用程式能解讀即可。CredentialsData 只會在啟動或加入時傳遞至 Android TV 應用程式。如果連線時重新設定,系統不會將密碼傳送至 Android TV 應用程式。
如要設定啟動憑證,必須定義 CredentialsData 並傳遞至 LaunchOptions 物件。在 CastOptionsProvider.kt 檔案的 getCastOptions 方法中新增下列程式碼:
import com.google.android.gms.cast.CredentialsData
...
val credentialsData = CredentialsData.Builder()
.setCredentials("{\"userId\": \"abc\"}")
.build()
val launchOptions = LaunchOptions.Builder()
...
.setCredentialsData(credentialsData)
.build()
在 LoadRequest 上設定憑證
如果網頁接收器應用程式和 Android TV 應用程式處理 credentials 的方式不同,您可能需要為每個應用程式定義不同的 credentials。為此,請在 LocalPlayerActivity.kt 檔案的 loadRemoteMedia 函式下方新增下列程式碼:
remoteMediaClient.load(MediaLoadRequestData.Builder()
...
.setCredentials("user-credentials")
.setAtvCredentials("atv-user-credentials")
.build())
視傳送端投放內容的接收端應用程式而定,SDK 現在會自動處理目前工作階段要使用的憑證。
測試 Cast Connect
在 Chromecast (支援 Google TV) 上安裝 Android TV APK 的步驟
- 找出 Android TV 裝置的 IP 位址。通常可以在「設定」>「網路與網際網路」>「(裝置連上的網路名稱)」下方找到。右側會顯示詳細資料和裝置在網路上的 IP。
- 使用裝置的 IP 位址,透過終端機以 ADB 連線至裝置:
$ adb connect <device_ip_address>:5555
- 在終端機視窗中,前往本程式碼研究室開頭下載的程式碼研究室範例頂層資料夾。例如:
$ cd Desktop/android_codelab_src
- 執行下列指令,將這個資料夾中的 .apk 檔案安裝到 Android TV:
$ adb -s <device_ip_address>:5555 install android-tv-app.apk
- 現在 Android TV 裝置的「你的應用程式」選單中,應該會顯示名為「投放影片」的應用程式。
- 返回 Android Studio 專案,然後按一下「Run」按鈕,在實體行動裝置上安裝及執行傳送端應用程式。按一下右上角的投放圖示,然後從可用選項中選取 Android TV 裝置。現在應該會在 Android TV 裝置上看到 Android TV 應用程式啟動,播放影片時應該可以使用 Android TV 遙控器控制影片播放。
12. 自訂 Google Cast 小工具
你可以自訂投放小工具,設定顏色、按鈕樣式、文字和縮圖外觀,以及選擇要顯示的按鈕類型。
更新「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>
宣告下列自訂主題:
<!-- 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. 恭喜
您現在已瞭解如何使用 Android 上的 Cast SDK 小工具,為影片應用程式啟用 Cast 功能。
詳情請參閱 Android 傳送端開發人員指南。