本開發人員指南說明如何使用 Android Sender SDK,在 Android 傳送端應用程式中新增 Google Cast 支援功能。
行動裝置或筆記型電腦是控制播放的傳送端,而 Google Cast 裝置則是顯示電視內容的接收端。
傳送器架構是指在傳送器上執行階段出現的 Cast 類別程式庫二進位檔和相關資源。「傳送端應用程式」或「投放應用程式」是指在傳送端上執行的應用程式。「Web Receiver 應用程式」是指在支援 Cast 的裝置上執行的 HTML 應用程式。
傳送端架構會使用非同步回呼設計,向傳送端應用程式通知事件,並在 Cast 應用程式生命週期的不同狀態之間進行轉換。
應用程式流程
下列步驟說明傳送端 Android 應用程式的一般高階執行流程:
- Cast 架構會根據
Activity
生命週期自動啟動MediaRouter
裝置探索。 - 使用者點選「投放」按鈕時,架構會顯示「投放」對話方塊,列出所偵測到的投放裝置。
- 使用者選取投放裝置時,架構會嘗試在投放裝置上啟動 Web Receiver 應用程式。
- 此架構會在傳送端應用程式中叫用回呼,確認 Web Receiver 應用程式是否已啟動。
- 這個架構會在傳送端和 Web Receiver 應用程式之間建立通訊管道。
- 這個架構會使用通訊管道,在 Web Receiver 上載入及控制媒體播放。
- 此架構會在傳送端和 Web Receiver 之間同步媒體播放狀態:當使用者執行傳送端 UI 動作時,架構會將這些媒體控制項要求傳遞至 Web Receiver,而當 Web Receiver 傳送媒體狀態更新時,架構會更新傳送端 UI 的狀態。
- 當使用者按下投放按鈕以斷開與投放裝置的連線時,架構會將傳送端應用程式與 Web 接收器斷開連線。
如需 Google Cast Android SDK 中所有類別、方法和事件的完整清單,請參閱 Android 版 Google Cast Sender API 參考資料。下列章節將說明如何在 Android 應用程式中加入 Cast。
設定 Android 資訊清單
應用程式的 AndroidManifest.xml 檔案需要為 Cast SDK 設定下列元素:
uses-sdk
設定 Cast SDK 支援的最低和目標 Android API 級別。目前最低為 API 級別 23,目標則是 API 級別 34。
<uses-sdk
android:minSdkVersion="23"
android:targetSdkVersion="34" />
android:theme
根據 Android 最低 SDK 版本設定應用程式主題。舉例來說,如果您未實作自己的主題,在指定 Lollipop 之前的 Android SDK 最低版本時,應使用 Theme.AppCompat
的變體。
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/Theme.AppCompat" >
...
</application>
初始化 Cast 結構定義
此架構包含全域單例模式物件 CastContext
,可協調所有架構互動。
您的應用程式必須實作 OptionsProvider
介面,才能提供初始化 CastContext
單例所需的選項。OptionsProvider
會提供 CastOptions
的例項,其中包含會影響架構行為的選項。其中最重要的是 Web Receiver 應用程式 ID,用於篩選探索結果,並在 Cast 工作階段啟動時啟動 Web Receiver 應用程式。
class CastOptionsProvider : OptionsProvider { override fun getCastOptions(context: Context): CastOptions { return Builder() .setReceiverApplicationId(context.getString(R.string.app_id)) .build() } override fun getAdditionalSessionProviders(context: Context): List<SessionProvider>? { return null } }
public class CastOptionsProvider implements OptionsProvider { @Override public CastOptions getCastOptions(Context context) { CastOptions castOptions = new CastOptions.Builder() .setReceiverApplicationId(context.getString(R.string.app_id)) .build(); return castOptions; } @Override public List<SessionProvider> getAdditionalSessionProviders(Context context) { return null; } }
您必須在傳送端應用程式的 AndroidManifest.xml 檔案中,將實作的 OptionsProvider
完整名稱宣告為中繼資料欄位:
<application>
...
<meta-data
android:name=
"com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
android:value="com.foo.CastOptionsProvider" />
</application>
呼叫 CastContext.getSharedInstance()
時,系統會延遲初始化 CastContext
。
class MyActivity : FragmentActivity() { override fun onCreate(savedInstanceState: Bundle?) { val castContext = CastContext.getSharedInstance(this) } }
public class MyActivity extends FragmentActivity { @Override public void onCreate(Bundle savedInstanceState) { CastContext castContext = CastContext.getSharedInstance(this); } }
投放使用者體驗小工具
Cast 架構會提供符合 Cast 設計檢查清單的小工具:
簡介疊加畫面:此架構會提供自訂檢視畫面
IntroductoryOverlay
,讓使用者在第一次找到接收器時,能注意到投放按鈕。傳送端應用程式可以自訂標題文字的文字和位置。投放按鈕:無論 Cast 裝置是否可用,投放按鈕都會顯示。使用者第一次點選「投放」按鈕時,系統會顯示「投放」對話方塊,列出所偵測到的裝置。當使用者在裝置連線時按下 Cast 按鈕,系統會顯示目前的媒體中繼資料 (例如標題、錄音室名稱和縮圖),或讓使用者斷開與 Cast 裝置的連線。「投放按鈕」有時也稱為「投放圖示」。
迷你控制器:當使用者投放內容,並已從目前的內容頁面或展開的控制器前往傳送端應用程式中的其他畫面時,系統會在畫面底部顯示迷你控制器,方便使用者查看目前投放的媒體中繼資料,並控制播放作業。
展開式控制器:當使用者投放內容時,如果點選媒體通知或迷你控制器,系統會啟動展開式控制器,顯示目前播放的媒體中繼資料,並提供多個按鈕來控制媒體播放。
通知:僅限 Android 裝置。當使用者投放內容並離開傳送端應用程式時,系統會顯示媒體通知,顯示目前投放的媒體中繼資料和播放控制項。
螢幕鎖定:僅限 Android 裝置。當使用者投放內容並前往鎖定畫面 (或裝置逾時) 時,系統會顯示媒體鎖定畫面控制項,顯示目前投放的媒體中繼資料和播放控制項。
以下指南將說明如何將這些小工具新增至應用程式。
新增投放按鈕
Android MediaRouter
API 可讓您在次要裝置上顯示及播放媒體。使用 MediaRouter
API 的 Android 應用程式應在使用者介面中加入投放按鈕,讓使用者選取媒體路徑,在次要裝置 (例如投放裝置) 上播放媒體。
這個架構可讓您輕鬆將 MediaRouteButton
新增為 Cast button
。您應先在定義選單的 XML 檔案中新增選單項目或 MediaRouteButton
,然後使用 CastButtonFactory
將其連結至架構。
// To add a Cast button, add the following snippet.
// menu.xml
<item
android:id="@+id/media_route_menu_item"
android:title="@string/media_route_menu_title"
app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
app:showAsAction="always" />
// Then override the onCreateOptionMenu() for each of your activities. // MyActivity.kt override fun onCreateOptionsMenu(menu: Menu): Boolean { super.onCreateOptionsMenu(menu) menuInflater.inflate(R.menu.main, menu) CastButtonFactory.setUpMediaRouteButton( applicationContext, menu, R.id.media_route_menu_item ) return true }
// Then override the onCreateOptionMenu() for each of your activities. // MyActivity.java @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); getMenuInflater().inflate(R.menu.main, menu); CastButtonFactory.setUpMediaRouteButton(getApplicationContext(), menu, R.id.media_route_menu_item); return true; }
接著,如果 Activity
繼承自 FragmentActivity
,您可以將 MediaRouteButton
新增至版面配置。
// activity_layout.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal" >
<androidx.mediarouter.app.MediaRouteButton
android:id="@+id/media_route_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:mediaRouteTypes="user"
android:visibility="gone" />
</LinearLayout>
// MyActivity.kt override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_layout) mMediaRouteButton = findViewById<View>(R.id.media_route_button) as MediaRouteButton CastButtonFactory.setUpMediaRouteButton(applicationContext, mMediaRouteButton) mCastContext = CastContext.getSharedInstance(this) }
// MyActivity.java @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_layout); mMediaRouteButton = (MediaRouteButton) findViewById(R.id.media_route_button); CastButtonFactory.setUpMediaRouteButton(getApplicationContext(), mMediaRouteButton); mCastContext = CastContext.getSharedInstance(this); }
如要使用主題設定投放按鈕的外觀,請參閱「自訂投放按鈕」。
設定裝置探索
裝置探索功能完全由 CastContext
管理。初始化 CastContext 時,傳送端應用程式會指定 Web Receiver 應用程式 ID,並可選擇在 CastOptions
中設定 supportedNamespaces
,要求命名空間篩選。CastContext
會在內部保留 MediaRouter
的參照,並在下列情況下啟動探索程序:
- 根據用於平衡裝置探索延遲時間和電池用量的演算法,當傳送端應用程式進入前景時,系統偶爾會自動啟動探索功能。
- 開啟投放對話方塊。
- Cast SDK 正在嘗試復原 Cast 工作階段。
當投放對話方塊關閉或傳送端應用程式進入背景時,系統會停止探索程序。
class CastOptionsProvider : OptionsProvider { companion object { const val CUSTOM_NAMESPACE = "urn:x-cast:custom_namespace" } override fun getCastOptions(appContext: Context): CastOptions { val supportedNamespaces: MutableList<String> = ArrayList() supportedNamespaces.add(CUSTOM_NAMESPACE) return CastOptions.Builder() .setReceiverApplicationId(context.getString(R.string.app_id)) .setSupportedNamespaces(supportedNamespaces) .build() } override fun getAdditionalSessionProviders(context: Context): List<SessionProvider>? { return null } }
class CastOptionsProvider implements OptionsProvider { public static final String CUSTOM_NAMESPACE = "urn:x-cast:custom_namespace"; @Override public CastOptions getCastOptions(Context appContext) { List<String> supportedNamespaces = new ArrayList<>(); supportedNamespaces.add(CUSTOM_NAMESPACE); CastOptions castOptions = new CastOptions.Builder() .setReceiverApplicationId(context.getString(R.string.app_id)) .setSupportedNamespaces(supportedNamespaces) .build(); return castOptions; } @Override public List<SessionProvider> getAdditionalSessionProviders(Context context) { return null; } }
工作階段管理機制的運作方式
Cast SDK 會介紹 Cast 工作階段的概念,建立工作階段時會結合連線至裝置、啟動 (或加入) Web Receiver 應用程式、連線至該應用程式,以及初始化媒體控制管道的步驟。如要進一步瞭解投放工作階段和 Web Receiver 生命週期,請參閱「應用程式生命週期指南」。
工作階段由 SessionManager
類別管理,應用程式可透過 CastContext.getSessionManager()
存取該類別。個別工作階段會以 Session
類別的子類別表示。舉例來說,CastSession
代表使用投放裝置的工作階段。應用程式可以透過 SessionManager.getCurrentCastSession()
存取目前有效的 Cast 工作階段。
應用程式可以使用 SessionManagerListener
類別監控工作階段事件,例如建立、暫停、繼續和終止。在工作階段處於活動狀態時,架構會自動嘗試從異常/突然終止的狀態繼續執行。
系統會根據 MediaRouter
對話方塊的使用者手勢,自動建立及關閉工作階段。
為進一步瞭解 Cast 啟動錯誤,應用程式可以使用 CastContext#getCastReasonCodeForCastStatusCode(int)
將工作階段啟動錯誤轉換為 CastReasonCodes
。請注意,部分工作階段啟動錯誤 (例如 CastReasonCodes#CAST_CANCELLED
) 是預期行為,不應記錄為錯誤。
如果您需要瞭解工作階段的狀態變更,可以實作 SessionManagerListener
。這個範例會監聽 Activity
中的 CastSession
可用性。
class MyActivity : Activity() { private var mCastSession: CastSession? = null private lateinit var mCastContext: CastContext private lateinit var mSessionManager: SessionManager private val mSessionManagerListener: SessionManagerListener<CastSession> = SessionManagerListenerImpl() private inner class SessionManagerListenerImpl : SessionManagerListener<CastSession?> { override fun onSessionStarting(session: CastSession?) {} override fun onSessionStarted(session: CastSession?, sessionId: String) { invalidateOptionsMenu() } override fun onSessionStartFailed(session: CastSession?, error: Int) { val castReasonCode = mCastContext.getCastReasonCodeForCastStatusCode(error) // Handle error } override fun onSessionSuspended(session: CastSession?, reason Int) {} override fun onSessionResuming(session: CastSession?, sessionId: String) {} override fun onSessionResumed(session: CastSession?, wasSuspended: Boolean) { invalidateOptionsMenu() } override fun onSessionResumeFailed(session: CastSession?, error: Int) {} override fun onSessionEnding(session: CastSession?) {} override fun onSessionEnded(session: CastSession?, error: Int) { finish() } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) mCastContext = CastContext.getSharedInstance(this) mSessionManager = mCastContext.sessionManager mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession::class.java) } override fun onResume() { super.onResume() mCastSession = mSessionManager.currentCastSession } override fun onDestroy() { super.onDestroy() mSessionManager.removeSessionManagerListener(mSessionManagerListener, CastSession::class.java) } }
public class MyActivity extends Activity { private CastContext mCastContext; private CastSession mCastSession; private SessionManager mSessionManager; private SessionManagerListener<CastSession> mSessionManagerListener = new SessionManagerListenerImpl(); private class SessionManagerListenerImpl implements SessionManagerListener<CastSession> { @Override public void onSessionStarting(CastSession session) {} @Override public void onSessionStarted(CastSession session, String sessionId) { invalidateOptionsMenu(); } @Override public void onSessionStartFailed(CastSession session, int error) { int castReasonCode = mCastContext.getCastReasonCodeForCastStatusCode(error); // Handle error } @Override public void onSessionSuspended(CastSession session, int reason) {} @Override public void onSessionResuming(CastSession session, String sessionId) {} @Override public void onSessionResumed(CastSession session, boolean wasSuspended) { invalidateOptionsMenu(); } @Override public void onSessionResumeFailed(CastSession session, int error) {} @Override public void onSessionEnding(CastSession session) {} @Override public void onSessionEnded(CastSession session, int error) { finish(); } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mCastContext = CastContext.getSharedInstance(this); mSessionManager = mCastContext.getSessionManager(); mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession.class); } @Override protected void onResume() { super.onResume(); mCastSession = mSessionManager.getCurrentCastSession(); } @Override protected void onDestroy() { super.onDestroy(); mSessionManager.removeSessionManagerListener(mSessionManagerListener, CastSession.class); } }
串流傳輸
保留工作階段狀態是串流轉移的基礎,使用者可以透過語音指令、Google Home 應用程式或智慧螢幕,在裝置之間移動現有的音訊和視訊串流。媒體在一個裝置 (來源) 上停止播放,並在另一個裝置 (目的地) 上繼續播放。任何搭載最新韌體的 Cast 裝置,都可以在串流轉移中擔任來源或目的地。
如要在串流轉移或擴充期間取得新的目的地裝置,請使用 CastSession#addCastListener
註冊 Cast.Listener
。然後在 onDeviceNameChanged
回呼期間呼叫 CastSession#getCastDevice()
。
詳情請參閱「在網路接收器上進行串流轉移」。
自動重新連線
此架構提供 ReconnectionService
,可由傳送端應用程式啟用,在許多微妙的極端情況下處理重新連線,例如:
- 復原暫時斷線的 Wi-Fi 連線
- 從裝置休眠狀態復原
- 從背景應用程式復原
- 在應用程式當機時復原
這項服務預設為開啟,您可以在 CastOptions.Builder
中關閉。
如果在 Gradle 檔案中啟用自動合併功能,這項服務就會自動合併至應用程式的資訊清單。
在有媒體工作階段時,架構會啟動服務,並在媒體工作階段結束時停止服務。
媒體控制功能的運作方式
Cast 架構已淘汰 Cast 2.x 中的 RemoteMediaPlayer
類別,改用 RemoteMediaClient
這個新類別,後者可在一系列更方便的 API 中提供相同的功能,並避免您必須傳入 GoogleApiClient。
當應用程式與支援媒體命名空間的 Web Receiver 應用程式建立 CastSession
時,架構會自動建立 RemoteMediaClient
的例項;應用程式可透過在 CastSession
例項上呼叫 getRemoteMediaClient()
方法來存取該例項。
所有向網頁接收器發出要求的 RemoteMediaClient
方法都會傳回 PendingResult 物件,可用於追蹤該要求。
預期 RemoteMediaClient
的例項可能會由應用程式的多個部分共用,以及架構的某些內部元件,例如持續性 mini 控制器和通知服務。為此,這個例項支援註冊多個 RemoteMediaClient.Listener
例項。
設定媒體中繼資料
MediaMetadata
類別代表要投放的媒體項目資訊。以下範例會建立電影的新 MediaMetadata 例項,並設定標題、副標題和兩張圖片。
val movieMetadata = MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE) movieMetadata.putString(MediaMetadata.KEY_TITLE, mSelectedMedia.getTitle()) movieMetadata.putString(MediaMetadata.KEY_SUBTITLE, mSelectedMedia.getStudio()) movieMetadata.addImage(WebImage(Uri.parse(mSelectedMedia.getImage(0)))) movieMetadata.addImage(WebImage(Uri.parse(mSelectedMedia.getImage(1))))
MediaMetadata movieMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE); movieMetadata.putString(MediaMetadata.KEY_TITLE, mSelectedMedia.getTitle()); movieMetadata.putString(MediaMetadata.KEY_SUBTITLE, mSelectedMedia.getStudio()); movieMetadata.addImage(new WebImage(Uri.parse(mSelectedMedia.getImage(0)))); movieMetadata.addImage(new WebImage(Uri.parse(mSelectedMedia.getImage(1))));
如要瞭解如何使用含有媒體中繼資料的圖片,請參閱圖片選取一節。
載入媒體
應用程式可以載入媒體項目,如以下程式碼所示。首先,請使用 MediaInfo.Builder
搭配媒體的中繼資料,建立 MediaInfo
例項。從目前的 CastSession
取得 RemoteMediaClient
,然後將 MediaInfo
載入該 RemoteMediaClient
。使用 RemoteMediaClient
播放、暫停或以其他方式控制在 Web Receiver 上執行的媒體播放器應用程式。
val mediaInfo = MediaInfo.Builder(mSelectedMedia.getUrl()) .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED) .setContentType("videos/mp4") .setMetadata(movieMetadata) .setStreamDuration(mSelectedMedia.getDuration() * 1000) .build() val remoteMediaClient = mCastSession.getRemoteMediaClient() remoteMediaClient.load(MediaLoadRequestData.Builder().setMediaInfo(mediaInfo).build())
MediaInfo mediaInfo = new MediaInfo.Builder(mSelectedMedia.getUrl()) .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED) .setContentType("videos/mp4") .setMetadata(movieMetadata) .setStreamDuration(mSelectedMedia.getDuration() * 1000) .build(); RemoteMediaClient remoteMediaClient = mCastSession.getRemoteMediaClient(); remoteMediaClient.load(new MediaLoadRequestData.Builder().setMediaInfo(mediaInfo).build());
另請參閱「使用媒體音軌」一節。
4K 影片格式
如要查看媒體的影片格式,請使用 MediaStatus 中的 getVideoInfo()
取得 VideoInfo
的目前例項。這個例項包含 HDR 電視格式類型,以及以像素為單位的顯示高度和寬度。4K 格式的變化版本會以常數 HDR_TYPE_*
表示。
透過遙控通知傳送至多部裝置
當使用者投放時,同一個網路中的其他 Android 裝置也會收到通知,讓他們控制播放內容。任何收到這類通知的裝置使用者,都可以在「設定」應用程式中依序點選「Google」>「Google Cast」>「顯示遠端控制通知」,關閉該裝置的通知。(通知包含「設定」應用程式的捷徑)。詳情請參閱「投放遙控器通知」。
新增迷你控制器
根據投放設計檢查清單,發送端應用程式應提供持續性控制項,也就是「迷你控制器」,當使用者從目前的內容頁面瀏覽至發送端應用程式的其他部分時,應會顯示這項控制項。迷你控制項會向使用者顯示目前投放工作階段的提醒。輕觸迷你控制器後,使用者就能返回 Cast 全螢幕展開控制器檢視畫面。
此架構會提供自訂檢視區塊 MiniControllerFragment,您可以將其新增至要顯示迷你控制器的每個活動的版面配置檔案底部。
<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" />
當傳送端應用程式播放影片或音訊直播時,SDK 會自動顯示播放/停止按鈕,取代迷你控制器中的播放/暫停按鈕。
如要設定此自訂檢視畫面的標題和副標題文字外觀,以及選擇按鈕,請參閱「自訂迷你控制器」。
新增展開的控制器
Google Cast 設計檢查清單規定,傳送端應用程式必須為要投放的媒體提供展開式控制器。展開控制器是迷你控制器的全螢幕版本。
Cast SDK 為名為 ExpandedControllerActivity
的展開控制器提供小工具。這是您必須建立子類別才能新增投放按鈕的抽象類別。
首先,為展開的控制器建立新的選單資源檔案,以提供投放按鈕:
<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>
建立可擴充 ExpandedControllerActivity
的新類別。
class ExpandedControlsActivity : ExpandedControllerActivity() { override fun onCreateOptionsMenu(menu: Menu): Boolean { super.onCreateOptionsMenu(menu) menuInflater.inflate(R.menu.expanded_controller, menu) CastButtonFactory.setUpMediaRouteButton(this, menu, R.id.media_route_menu_item) return true } }
public class ExpandedControlsActivity extends ExpandedControllerActivity { @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); getMenuInflater().inflate(R.menu.expanded_controller, menu); CastButtonFactory.setUpMediaRouteButton(this, menu, R.id.media_route_menu_item); return true; } }
接著,在 application
標記中宣告應用程式資訊清單中的新活動:
<application>
...
<activity
android:name=".expandedcontrols.ExpandedControlsActivity"
android:label="@string/app_name"
android:launchMode="singleTask"
android:theme="@style/Theme.CastVideosDark"
android:screenOrientation="portrait"
android:parentActivityName="com.google.sample.cast.refplayer.VideoBrowserActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
</intent-filter>
</activity>
...
</application>
編輯 CastOptionsProvider
並變更 NotificationOptions
和 CastMediaOptions
,將目標活動設為新的活動:
override fun getCastOptions(context: Context): CastOptions? { val notificationOptions = NotificationOptions.Builder() .setTargetActivityClassName(ExpandedControlsActivity::class.java.name) .build() val mediaOptions = CastMediaOptions.Builder() .setNotificationOptions(notificationOptions) .setExpandedControllerActivityClassName(ExpandedControlsActivity::class.java.name) .build() return CastOptions.Builder() .setReceiverApplicationId(context.getString(R.string.app_id)) .setCastMediaOptions(mediaOptions) .build() }
public CastOptions getCastOptions(Context context) { NotificationOptions notificationOptions = new NotificationOptions.Builder() .setTargetActivityClassName(ExpandedControlsActivity.class.getName()) .build(); CastMediaOptions mediaOptions = new CastMediaOptions.Builder() .setNotificationOptions(notificationOptions) .setExpandedControllerActivityClassName(ExpandedControlsActivity.class.getName()) .build(); return new CastOptions.Builder() .setReceiverApplicationId(context.getString(R.string.app_id)) .setCastMediaOptions(mediaOptions) .build(); }
更新 LocalPlayerActivity
loadRemoteMedia
方法,以便在載入遠端媒體時顯示新的活動:
private fun loadRemoteMedia(position: Int, autoPlay: Boolean) { val remoteMediaClient = mCastSession?.remoteMediaClient ?: return remoteMediaClient.registerCallback(object : RemoteMediaClient.Callback() { override fun onStatusUpdated() { val intent = Intent(this@LocalPlayerActivity, ExpandedControlsActivity::class.java) startActivity(intent) remoteMediaClient.unregisterCallback(this) } }) remoteMediaClient.load( MediaLoadRequestData.Builder() .setMediaInfo(mSelectedMedia) .setAutoplay(autoPlay) .setCurrentTime(position.toLong()).build() ) }
private void loadRemoteMedia(int position, boolean autoPlay) { if (mCastSession == null) { return; } final RemoteMediaClient remoteMediaClient = mCastSession.getRemoteMediaClient(); if (remoteMediaClient == null) { return; } remoteMediaClient.registerCallback(new RemoteMediaClient.Callback() { @Override public void onStatusUpdated() { Intent intent = new Intent(LocalPlayerActivity.this, ExpandedControlsActivity.class); startActivity(intent); remoteMediaClient.unregisterCallback(this); } }); remoteMediaClient.load(new MediaLoadRequestData.Builder() .setMediaInfo(mSelectedMedia) .setAutoplay(autoPlay) .setCurrentTime(position).build()); }
當傳送端應用程式播放影片或音訊直播時,SDK 會自動在展開的控制器中,將播放/暫停按鈕替換為播放/停止按鈕。
如要使用主題設定外觀、選擇要顯示的按鈕,以及新增自訂按鈕,請參閱「自訂展開式控制器」。
音量控制項
此架構會自動管理傳送端應用程式的音量。架構會自動同步處理傳送端和 Web Receiver 應用程式,讓傳送端 UI 一律回報 Web Receiver 指定的音量。
實體按鈕音量控制
在 Android 上,如果裝置使用 Jelly Bean 以上版本,則預設可透過傳送端裝置上的實體按鈕,變更 Web 接收器上 Cast 工作階段的音量。
在 Jelly Bean 之前,實體按鈕的音量控制
如要在 Jelly Bean 以下版本的 Android 裝置上使用實體音量鍵控制 Web Receiver 裝置音量,傳送端應用程式應在其活動中覆寫 dispatchKeyEvent
,並呼叫 CastContext.onDispatchVolumeKeyEventBeforeJellyBean()
:
class MyActivity : FragmentActivity() { override fun dispatchKeyEvent(event: KeyEvent): Boolean { return (CastContext.getSharedInstance(this) .onDispatchVolumeKeyEventBeforeJellyBean(event) || super.dispatchKeyEvent(event)) } }
class MyActivity extends FragmentActivity { @Override public boolean dispatchKeyEvent(KeyEvent event) { return CastContext.getSharedInstance(this) .onDispatchVolumeKeyEventBeforeJellyBean(event) || super.dispatchKeyEvent(event); } }
在通知和螢幕鎖定畫面中加入媒體控制選項
僅限 Android:Google Cast 設計檢查清單規定,在傳送端應用程式投放內容但未獲得焦點時,傳送端應用程式應在通知和鎖定畫面中實作媒體控制項。此架構提供 MediaNotificationService
和 MediaIntentReceiver
,協助傳送端應用程式在通知和螢幕鎖定畫面中建構媒體控制項。
MediaNotificationService
會在傳送端投放時執行,並顯示通知,其中包含圖片縮圖、目前投放項目的相關資訊、播放/暫停按鈕和停止按鈕。
MediaIntentReceiver
是 BroadcastReceiver
,可處理來自通知的使用者動作。
應用程式可以透過 NotificationOptions
設定螢幕鎖定畫面的通知和媒體控制項。應用程式可設定通知中要顯示的控制按鈕,以及使用者輕觸通知時要開啟的 Activity
。如果未明確提供動作,系統會使用預設值 MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK
和 MediaIntentReceiver.ACTION_STOP_CASTING
。
// Example showing 4 buttons: "rewind", "play/pause", "forward" and "stop casting". val buttonActions: MutableList<String> = ArrayList() buttonActions.add(MediaIntentReceiver.ACTION_REWIND) buttonActions.add(MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK) buttonActions.add(MediaIntentReceiver.ACTION_FORWARD) buttonActions.add(MediaIntentReceiver.ACTION_STOP_CASTING) // Showing "play/pause" and "stop casting" in the compat view of the notification. val compatButtonActionsIndices = intArrayOf(1, 3) // Builds a notification with the above actions. Each tap on the "rewind" and "forward" buttons skips 30 seconds. // Tapping on the notification opens an Activity with class VideoBrowserActivity. val notificationOptions = NotificationOptions.Builder() .setActions(buttonActions, compatButtonActionsIndices) .setSkipStepMs(30 * DateUtils.SECOND_IN_MILLIS) .setTargetActivityClassName(VideoBrowserActivity::class.java.name) .build()
// Example showing 4 buttons: "rewind", "play/pause", "forward" and "stop casting". List<String> buttonActions = new ArrayList<>(); buttonActions.add(MediaIntentReceiver.ACTION_REWIND); buttonActions.add(MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK); buttonActions.add(MediaIntentReceiver.ACTION_FORWARD); buttonActions.add(MediaIntentReceiver.ACTION_STOP_CASTING); // Showing "play/pause" and "stop casting" in the compat view of the notification. int[] compatButtonActionsIndices = new int[]{1, 3}; // Builds a notification with the above actions. Each tap on the "rewind" and "forward" buttons skips 30 seconds. // Tapping on the notification opens an Activity with class VideoBrowserActivity. NotificationOptions notificationOptions = new NotificationOptions.Builder() .setActions(buttonActions, compatButtonActionsIndices) .setSkipStepMs(30 * DateUtils.SECOND_IN_MILLIS) .setTargetActivityClassName(VideoBrowserActivity.class.getName()) .build();
系統預設會啟用顯示通知和螢幕鎖定畫面中的媒體控制項功能,您可以呼叫 setNotificationOptions
,並在 CastMediaOptions.Builder
中使用空值來停用這項功能。目前只要開啟通知功能,系統就會開啟螢幕鎖定功能。
// ... continue with the NotificationOptions built above val mediaOptions = CastMediaOptions.Builder() .setNotificationOptions(notificationOptions) .build() val castOptions: CastOptions = Builder() .setReceiverApplicationId(context.getString(R.string.app_id)) .setCastMediaOptions(mediaOptions) .build()
// ... continue with the NotificationOptions built above CastMediaOptions mediaOptions = new CastMediaOptions.Builder() .setNotificationOptions(notificationOptions) .build(); CastOptions castOptions = new CastOptions.Builder() .setReceiverApplicationId(context.getString(R.string.app_id)) .setCastMediaOptions(mediaOptions) .build();
當傳送端應用程式播放影片或音訊直播串流時,SDK 會自動在通知控制項上顯示播放/停止按鈕,但不會在鎖定螢幕控制項上顯示。
注意:如要在 Lollipop 之前的裝置上顯示螢幕鎖定畫面控制項,RemoteMediaClient
會自動代您要求音訊焦點。
處理錯誤
因此,發送端應用程式必須處理所有錯誤回呼,並決定 Cast 生命週期各個階段的最佳回應。應用程式可以向使用者顯示錯誤對話方塊,或決定中斷與 Web Receiver 的連線。