此網頁包含程式碼片段和其可用功能的說明 自訂 Android TV 接收器應用程式。
設定程式庫
如要為 Android TV 應用程式提供 Cast Connect API,請按照下列步驟操作:
-
在應用程式模組目錄中開啟
build.gradle
檔案。 -
確認
google()
已包含在列出的repositories
中。repositories { google() }
-
依據應用程式的目標裝置類型,加入最新版本
將程式庫部署至依附元件:
-
Android Receiver 應用程式:
dependencies { implementation 'com.google.android.gms:play-services-cast-tv:21.1.0' implementation 'com.google.android.gms:play-services-cast:21.5.0' }
-
Android 寄件者應用程式:
dependencies { implementation 'com.google.android.gms:play-services-cast:21.1.0' implementation 'com.google.android.gms:play-services-cast-framework:21.5.0' }
-
Android Receiver 應用程式:
-
儲存變更,然後按一下
Sync Project with Gradle Files
點選工具列中的「校對這份文件」
-
請確定您的
Podfile
指定google-cast-sdk
4.8.3 或更高 -
指定 iOS 14 以上版本。請參閱版本資訊
,掌握更多詳細資訊。
platform: ios, '14' def target_pods pod 'google-cast-sdk', '~>4.8.3' end
- 需要 Chromium 瀏覽器 M87 以上版本。
-
將 Web Sender API 程式庫新增至專案
<script src="//www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1"></script>
AndroidX 需求條件
新版 Google Play 服務必須更新才能使用
androidx
命名空間。請按照下列說明操作:
遷移至 AndroidX
Android TV 應用程式 (必備條件
如要在 Android TV 應用程式中支援 Cast Connect,您必須建立 支援來自媒體工作階段的事件媒體工作階段提供的資料 會提供位置、播放狀態等基本資訊, 媒體狀態。Cast Connect 程式庫也會使用你的媒體工作階段 並在收到來自寄件者的特定訊息時發出通知,例如暫停。
如要進一步瞭解媒體工作階段及如何初始化媒體工作階段, 請參閱 相關媒體課程指南。
媒體工作階段生命週期
您的應用程式應該在開始播放時建立媒體工作階段,並於出現時將其放開 因此無法進一步控制例如,如果您的應用程式是影片應用程式 應該在使用者結束播放活動時釋出工作階段 (兩者之一是 選取「返回」瀏覽其他內容,或將應用程式設為背景執行。如果您的 是音樂應用程式,當您的應用程式不再播放時,就應該發布應用程式 媒體。
正在更新工作階段狀態
媒體工作階段中的資料應反映 廣告。例如,在暫停播放時,您應該更新播放 狀態與支援的動作下表列出了狀態 您須負責掌握最新的相關事項
MediaMetadataCompat
中繼資料欄位 | 說明 |
---|---|
METADATA_KEY_TITLE (必要) | 媒體標題。 |
METADATA_KEY_DISPLAY_SUBTITLE | 副標題。 |
METADATA_KEY_DISPLAY_ICON_URI | 圖示網址。 |
METADATA_KEY_DURATION (必要) | 媒體時間長度。 |
METADATA_KEY_MEDIA_URI | Content ID。 |
METADATA_KEY_ARTIST | 藝人。 |
METADATA_KEY_ALBUM | 專輯。 |
PlaybackStateCompat
必要方法 | 說明 |
---|---|
setActions() | 設定支援的媒體指令。 |
setState() | 設定播放狀態和目前位置。 |
MediaSessionCompat
必要方法 | 說明 |
---|---|
setRepeatMode() | 設定重播模式。 |
setShuffleMode() | 設定隨機播放模式。 |
setMetadata() | 設定媒體中繼資料。 |
setPlaybackState() | 設定播放狀態。 |
private fun updateMediaSession() { val metadata = MediaMetadataCompat.Builder() .putString(MediaMetadataCompat.METADATA_KEY_TITLE, "title") .putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "subtitle") .putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI, mMovie.getCardImageUrl()) .build() val playbackState = PlaybackStateCompat.Builder() .setState( PlaybackStateCompat.STATE_PLAYING, player.getPosition(), player.getPlaybackSpeed(), System.currentTimeMillis() ) .build() mediaSession.setMetadata(metadata) mediaSession.setPlaybackState(playbackState) }
private void updateMediaSession() { MediaMetadataCompat metadata = new MediaMetadataCompat.Builder() .putString(MediaMetadataCompat.METADATA_KEY_TITLE, "title") .putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "subtitle") .putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI,mMovie.getCardImageUrl()) .build(); PlaybackStateCompat playbackState = new PlaybackStateCompat.Builder() .setState( PlaybackStateCompat.STATE_PLAYING, player.getPosition(), player.getPlaybackSpeed(), System.currentTimeMillis()) .build(); mediaSession.setMetadata(metadata); mediaSession.setPlaybackState(playbackState); }
處理傳輸控制
應用程式應實作媒體工作階段傳輸控制回呼。 下表列出了需要處理的傳輸控制動作:
MediaSessionCompat.Callback
動作 | 說明 |
---|---|
onPlay() | 繼續 |
onPause() | 暫停 |
onSeekTo() | 跳轉至特定位置 |
onStop() | 停止目前的媒體 |
class MyMediaSessionCallback : MediaSessionCompat.Callback() { override fun onPause() { // Pause the player and update the play state. ... } override fun onPlay() { // Resume the player and update the play state. ... } override fun onSeekTo (long pos) { // Seek and update the play state. ... } ... } mediaSession.setCallback( MyMediaSessionCallback() );
public MyMediaSessionCallback extends MediaSessionCompat.Callback { public void onPause() { // Pause the player and update the play state. ... } public void onPlay() { // Resume the player and update the play state. ... } public void onSeekTo (long pos) { // Seek and update the play state. ... } ... } mediaSession.setCallback(new MyMediaSessionCallback());
設定 Cast 支援
當傳送端應用程式送出啟動要求時,系統就會建立意圖
包含應用程式命名空間您的應用程式會負責處理
以及建立
CastReceiverContext
敬上
物件。必須提供 CastReceiverContext
物件
,在 TV 應用程式執行期間與 Cast 互動。這個物件可讓電視
應用程式,接受來自任何已連結傳送者的投放媒體訊息。
Android TV 設定
新增啟動意圖篩選器
將新的意圖篩選器加到您要處理啟動的活動 來自傳送端應用程式的意圖:
<activity android:name="com.example.activity">
<intent-filter>
<action android:name="com.google.android.gms.cast.tv.action.LAUNCH" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
指定接收器選項提供者
您必須實作
ReceiverOptionsProvider
敬上
提供的
CastReceiverOptions
:
class MyReceiverOptionsProvider : ReceiverOptionsProvider { override fun getOptions(context: Context?): CastReceiverOptions { return CastReceiverOptions.Builder(context) .setStatusText("My App") .build() } }
public class MyReceiverOptionsProvider implements ReceiverOptionsProvider { @Override public CastReceiverOptions getOptions(Context context) { return new CastReceiverOptions.Builder(context) .setStatusText("My App") .build(); } }
然後在 AndroidManifest
中指定選項提供者:
<meta-data
android:name="com.google.android.gms.cast.tv.RECEIVER_OPTIONS_PROVIDER_CLASS_NAME"
android:value="com.example.mysimpleatvapplication.MyReceiverOptionsProvider" />
ReceiverOptionsProvider
會在下列情況用於提供 CastReceiverOptions
:
CastReceiverContext
已初始化。
投放接收器內容
將
CastReceiverContext
敬上
建立應用程式時:
override fun onCreate() { CastReceiverContext.initInstance(this) ... }
@Override public void onCreate() { CastReceiverContext.initInstance(this); ... }
在應用程式移至前景時啟動 CastReceiverContext
:
CastReceiverContext.getInstance().start()
CastReceiverContext.getInstance().start();
致電
stop()
敬上
的
CastReceiverContext
。
如果影片應用程式進入背景,不支援不支援的應用程式
背景播放:
// Player has stopped. CastReceiverContext.getInstance().stop()
// Player has stopped. CastReceiverContext.getInstance().stop();
此外,如果您的應用程式支援在背景播放,請呼叫 stop()
在 CastReceiverContext
於背景停止播放時。
我們強烈建議您從
androidx.lifecycle
敬上
管理通話的程式庫
CastReceiverContext.start()
。
和
CastReceiverContext.stop()
,
尤其是原生應用程式有多個活動時這可避免發生競速
在不同活動中呼叫 start()
和 stop()
時的情況。
// Create a LifecycleObserver class. class MyLifecycleObserver : DefaultLifecycleObserver { override fun onStart(owner: LifecycleOwner) { // App prepares to enter foreground. CastReceiverContext.getInstance().start() } override fun onStop(owner: LifecycleOwner) { // App has moved to the background or has terminated. CastReceiverContext.getInstance().stop() } } // Add the observer when your application is being created. class MyApplication : Application() { fun onCreate() { super.onCreate() // Initialize CastReceiverContext. CastReceiverContext.initInstance(this /* android.content.Context */) // Register LifecycleObserver ProcessLifecycleOwner.get().lifecycle.addObserver( MyLifecycleObserver()) } }
// Create a LifecycleObserver class. public class MyLifecycleObserver implements DefaultLifecycleObserver { @Override public void onStart(LifecycleOwner owner) { // App prepares to enter foreground. CastReceiverContext.getInstance().start(); } @Override public void onStop(LifecycleOwner owner) { // App has moved to the background or has terminated. CastReceiverContext.getInstance().stop(); } } // Add the observer when your application is being created. public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); // Initialize CastReceiverContext. CastReceiverContext.initInstance(this /* android.content.Context */); // Register LifecycleObserver ProcessLifecycleOwner.get().getLifecycle().addObserver( new MyLifecycleObserver()); } }
// In AndroidManifest.xml set MyApplication as the application class
<application
...
android:name=".MyApplication">
將 MediaSession 連結至 MediaManager
建立 Deployment 時
MediaSession
、
您也需要提供目前的 MediaSession
權杖
CastReceiverContext
。
以便知道要在哪裡傳送指令並擷取媒體播放狀態:
val mediaManager: MediaManager = receiverContext.getMediaManager() mediaManager.setSessionCompatToken(currentMediaSession.getSessionToken())
MediaManager mediaManager = receiverContext.getMediaManager(); mediaManager.setSessionCompatToken(currentMediaSession.getSessionToken());
當你因為播放無效而釋出MediaSession
時,應設定
出現空值權杖
MediaManager
:
myPlayer.stop() mediaSession.release() mediaManager.setSessionCompatToken(null)
myPlayer.stop(); mediaSession.release(); mediaManager.setSessionCompatToken(null);
如果您的應用程式可在背景執行時播放媒體,請
通話
CastReceiverContext.stop()
敬上
應用程式傳送至背景時,只應在應用程式運作時呼叫
並在背景繼續播放媒體。例如:
class MyLifecycleObserver : DefaultLifecycleObserver { ... // App has moved to the background. override fun onPause(owner: LifecycleOwner) { mIsBackground = true myStopCastReceiverContextIfNeeded() } } // Stop playback on the player. private fun myStopPlayback() { myPlayer.stop() myStopCastReceiverContextIfNeeded() } // Stop the CastReceiverContext when both the player has // stopped and the app has moved to the background. private fun myStopCastReceiverContextIfNeeded() { if (mIsBackground && myPlayer.isStopped()) { CastReceiverContext.getInstance().stop() } }
public class MyLifecycleObserver implements DefaultLifecycleObserver { ... // App has moved to the background. @Override public void onPause(LifecycleOwner owner) { mIsBackground = true; myStopCastReceiverContextIfNeeded(); } } // Stop playback on the player. private void myStopPlayback() { myPlayer.stop(); myStopCastReceiverContextIfNeeded(); } // Stop the CastReceiverContext when both the player has // stopped and the app has moved to the background. private void myStopCastReceiverContextIfNeeded() { if (mIsBackground && myPlayer.isStopped()) { CastReceiverContext.getInstance().stop(); } }
搭配 Cast Connect 使用 Exoplayer
如果使用
Exoplayer
,您可以使用
MediaSessionConnector
。
以自動維護工作階段及所有相關資訊,包括
播放狀態,而不是手動追蹤變更。
MediaSessionConnector.MediaButtonEventHandler
敬上
可用於處理 MediaButton 事件,方法是呼叫
setMediaButtonEventHandler(MediaButtonEventHandler)
。
其他硬體和網路
MediaSessionCompat.Callback
根據預設。
為了整合
MediaSessionConnector
敬上
,請將下列內容新增到您的玩家活動類別,或您處的任何地方
管理媒體工作階段:
class PlayerActivity : Activity() { private var mMediaSession: MediaSessionCompat? = null private var mMediaSessionConnector: MediaSessionConnector? = null private var mMediaManager: MediaManager? = null override fun onCreate(savedInstanceState: Bundle?) { ... mMediaSession = MediaSessionCompat(this, LOG_TAG) mMediaSessionConnector = MediaSessionConnector(mMediaSession!!) ... } override fun onStart() { ... mMediaManager = receiverContext.getMediaManager() mMediaManager!!.setSessionCompatToken(currentMediaSession.getSessionToken()) mMediaSessionConnector!!.setPlayer(mExoPlayer) mMediaSessionConnector!!.setMediaMetadataProvider(mMediaMetadataProvider) mMediaSession!!.isActive = true ... } override fun onStop() { ... mMediaSessionConnector!!.setPlayer(null) mMediaSession!!.release() mMediaManager!!.setSessionCompatToken(null) ... } }
public class PlayerActivity extends Activity { private MediaSessionCompat mMediaSession; private MediaSessionConnector mMediaSessionConnector; private MediaManager mMediaManager; @Override protected void onCreate(Bundle savedInstanceState) { ... mMediaSession = new MediaSessionCompat(this, LOG_TAG); mMediaSessionConnector = new MediaSessionConnector(mMediaSession); ... } @Override protected void onStart() { ... mMediaManager = receiverContext.getMediaManager(); mMediaManager.setSessionCompatToken(currentMediaSession.getSessionToken()); mMediaSessionConnector.setPlayer(mExoPlayer); mMediaSessionConnector.setMediaMetadataProvider(mMediaMetadataProvider); mMediaSession.setActive(true); ... } @Override protected void onStop() { ... mMediaSessionConnector.setPlayer(null); mMediaSession.release(); mMediaManager.setSessionCompatToken(null); ... } }
寄件者應用程式設定
啟用 Cast Connect 支援功能
透過 Cast Connect 支援功能更新傳送者應用程式後,即可宣告
方法是設定
androidReceiverCompatible
敬上
標記
LaunchOptions
。
設為 true
需要 play-services-cast-framework
版本
19.0.0
以上版本。
androidReceiverCompatible
旗標位於
LaunchOptions
(屬於 CastOptions
的一部分):
class CastOptionsProvider : OptionsProvider { override fun getCastOptions(context: Context?): CastOptions { val launchOptions: LaunchOptions = Builder() .setAndroidReceiverCompatible(true) .build() return CastOptions.Builder() .setLaunchOptions(launchOptions) ... .build() } }
public class CastOptionsProvider implements OptionsProvider { @Override public CastOptions getCastOptions(Context context) { LaunchOptions launchOptions = new LaunchOptions.Builder() .setAndroidReceiverCompatible(true) .build(); return new CastOptions.Builder() .setLaunchOptions(launchOptions) ... .build(); } }
需要 google-cast-sdk
v4.4.8
或
更高。
androidReceiverCompatible
旗標位於
GCKLaunchOptions
(屬於
GCKCastOptions
):
let options = GCKCastOptions(discoveryCriteria: GCKDiscoveryCriteria(applicationID: kReceiverAppID)) ... let launchOptions = GCKLaunchOptions() launchOptions.androidReceiverCompatible = true options.launchOptions = launchOptions GCKCastContext.setSharedInstanceWith(options)
需使用 Chromium 瀏覽器版本
M87
以上版本。
const context = cast.framework.CastContext.getInstance(); const castOptions = new cast.framework.CastOptions(); castOptions.receiverApplicationId = kReceiverAppID; castOptions.androidReceiverCompatible = true; context.setOptions(castOptions);
設定 Cast 開發人員控制台
設定 Android TV 應用程式
在裝置中新增 Android TV 應用程式的套件名稱 Cast 開發人員控制台 將其與 Cast 應用程式 ID 建立關聯。
註冊開發人員裝置
註冊您要使用的 Android TV 裝置序號 當中的開發作業 Cast 開發人員控制台。
如未註冊,Cast Connect 只適用於從以下網址安裝的應用程式: 基於安全考量,Google Play 商店。
進一步瞭解如何為 Cast 或 Android TV 裝置註冊投放功能 請參閱註冊頁面。
正在載入媒體
如果您已在 Android TV 應用程式中導入深層連結支援, 您應該已經在 Android TV 資訊清單中設定類似的定義:
<activity android:name="com.example.activity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="https"/>
<data android:host="www.example.com"/>
<data android:pathPattern=".*"/>
</intent-filter>
</activity>
依傳送者上的實體載入
在傳送者上,您可以設定媒體中的 entity
,藉此傳遞深層連結。
載入請求相關資訊:
val mediaToLoad = MediaInfo.Builder("some-id") .setEntity("https://example.com/watch/some-id") ... .build() val loadRequest = MediaLoadRequestData.Builder() .setMediaInfo(mediaToLoad) .setCredentials("user-credentials") ... .build() remoteMediaClient.load(loadRequest)
MediaInfo mediaToLoad = new MediaInfo.Builder("some-id") .setEntity("https://example.com/watch/some-id") ... .build(); MediaLoadRequestData loadRequest = new MediaLoadRequestData.Builder() .setMediaInfo(mediaToLoad) .setCredentials("user-credentials") ... .build(); remoteMediaClient.load(loadRequest);
let mediaInfoBuilder = GCKMediaInformationBuilder(entity: "https://example.com/watch/some-id") ... mediaInformation = mediaInfoBuilder.build() let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder() mediaLoadRequestDataBuilder.mediaInformation = mediaInformation mediaLoadRequestDataBuilder.credentials = "user-credentials" ... let mediaLoadRequestData = mediaLoadRequestDataBuilder.build() remoteMediaClient?.loadMedia(with: mediaLoadRequestData)
需使用 Chromium 瀏覽器版本
M87
以上版本。
let mediaInfo = new chrome.cast.media.MediaInfo('some-id"', 'video/mp4'); mediaInfo.entity = 'https://example.com/watch/some-id'; ... let request = new chrome.cast.media.LoadRequest(mediaInfo); request.credentials = 'user-credentials'; ... cast.framework.CastContext.getInstance().getCurrentSession().loadMedia(request);
系統會使用含有深層連結和套件名稱的意圖,傳送載入指令 是在 Play 管理中心定義的成效指標
為傳送者設定 ATV 憑證
您的 Web Receiver 應用程式和 Android TV 應用程式支援可能的情況有所不同
深層連結和 credentials
(例如處理驗證時)
兩個平台不同)。如要解決這個問題,您可以提供
Android TV 的 entity
和 credentials
:
val mediaToLoad = MediaInfo.Builder("some-id") .setEntity("https://example.com/watch/some-id") .setAtvEntity("myscheme://example.com/atv/some-id") ... .build() val loadRequest = MediaLoadRequestData.Builder() .setMediaInfo(mediaToLoad) .setCredentials("user-credentials") .setAtvCredentials("atv-user-credentials") ... .build() remoteMediaClient.load(loadRequest)
MediaInfo mediaToLoad = new MediaInfo.Builder("some-id") .setEntity("https://example.com/watch/some-id") .setAtvEntity("myscheme://example.com/atv/some-id") ... .build(); MediaLoadRequestData loadRequest = new MediaLoadRequestData.Builder() .setMediaInfo(mediaToLoad) .setCredentials("user-credentials") .setAtvCredentials("atv-user-credentials") ... .build(); remoteMediaClient.load(loadRequest);
let mediaInfoBuilder = GCKMediaInformationBuilder(entity: "https://example.com/watch/some-id") mediaInfoBuilder.atvEntity = "myscheme://example.com/atv/some-id" ... mediaInformation = mediaInfoBuilder.build() let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder() mediaLoadRequestDataBuilder.mediaInformation = mediaInformation mediaLoadRequestDataBuilder.credentials = "user-credentials" mediaLoadRequestDataBuilder.atvCredentials = "atv-user-credentials" ... let mediaLoadRequestData = mediaLoadRequestDataBuilder.build() remoteMediaClient?.loadMedia(with: mediaLoadRequestData)
需使用 Chromium 瀏覽器版本
M87
以上版本。
let mediaInfo = new chrome.cast.media.MediaInfo('some-id"', 'video/mp4'); mediaInfo.entity = 'https://example.com/watch/some-id'; mediaInfo.atvEntity = 'myscheme://example.com/atv/some-id'; ... let request = new chrome.cast.media.LoadRequest(mediaInfo); request.credentials = 'user-credentials'; request.atvCredentials = 'atv-user-credentials'; ... cast.framework.CastContext.getInstance().getCurrentSession().loadMedia(request);
如果 Web Receiver 應用程式啟動後,則會使用應用程式中的 entity
和 credentials
載入要求。不過,如果您的 Android TV 應用程式已啟動,SDK 會覆寫
含有atvEntity
和atvCredentials
的entity
及credentials
(如有指定)。
依 Content ID 或 MediaQueueData 載入
如未使用 entity
或 atvEntity
,且使用 Content ID 或
媒體資訊中的內容網址,或使用更詳細的「媒體載入」
您需要在「要求資料」中新增下列預先定義的意圖篩選器:
Android TV 應用程式:
<activity android:name="com.example.activity">
<intent-filter>
<action android:name="com.google.android.gms.cast.tv.action.LOAD"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
在傳送方端,與 load by entity 類似,
可以使用您的內容資訊來建立載入要求,並呼叫 load()
。
val mediaToLoad = MediaInfo.Builder("some-id").build() val loadRequest = MediaLoadRequestData.Builder() .setMediaInfo(mediaToLoad) .setCredentials("user-credentials") ... .build() remoteMediaClient.load(loadRequest)
MediaInfo mediaToLoad = new MediaInfo.Builder("some-id").build(); MediaLoadRequestData loadRequest = new MediaLoadRequestData.Builder() .setMediaInfo(mediaToLoad) .setCredentials("user-credentials") ... .build(); remoteMediaClient.load(loadRequest);
let mediaInfoBuilder = GCKMediaInformationBuilder(contentId: "some-id") ... mediaInformation = mediaInfoBuilder.build() let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder() mediaLoadRequestDataBuilder.mediaInformation = mediaInformation mediaLoadRequestDataBuilder.credentials = "user-credentials" ... let mediaLoadRequestData = mediaLoadRequestDataBuilder.build() remoteMediaClient?.loadMedia(with: mediaLoadRequestData)
需使用 Chromium 瀏覽器版本
M87
以上版本。
let mediaInfo = new chrome.cast.media.MediaInfo('some-id"', 'video/mp4'); ... let request = new chrome.cast.media.LoadRequest(mediaInfo); ... cast.framework.CastContext.getInstance().getCurrentSession().loadMedia(request);
處理載入要求
如要在活動中處理這些載入要求,您需要處理意圖 呼叫事件:
class MyActivity : Activity() { override fun onStart() { super.onStart() val mediaManager = CastReceiverContext.getInstance().getMediaManager() // Pass the intent to the SDK. You can also do this in onCreate(). if (mediaManager.onNewIntent(intent)) { // If the SDK recognizes the intent, you should early return. return } // If the SDK doesn't recognize the intent, you can handle the intent with // your own logic. ... } // For some cases, a new load intent triggers onNewIntent() instead of // onStart(). override fun onNewIntent(intent: Intent) { val mediaManager = CastReceiverContext.getInstance().getMediaManager() // Pass the intent to the SDK. You can also do this in onCreate(). if (mediaManager.onNewIntent(intent)) { // If the SDK recognizes the intent, you should early return. return } // If the SDK doesn't recognize the intent, you can handle the intent with // your own logic. ... } }
public class MyActivity extends Activity { @Override protected void onStart() { super.onStart(); MediaManager mediaManager = CastReceiverContext.getInstance().getMediaManager(); // Pass the intent to the SDK. You can also do this in onCreate(). if (mediaManager.onNewIntent(getIntent())) { // If the SDK recognizes the intent, you should early return. return; } // If the SDK doesn't recognize the intent, you can handle the intent with // your own logic. ... } // For some cases, a new load intent triggers onNewIntent() instead of // onStart(). @Override protected void onNewIntent(Intent intent) { MediaManager mediaManager = CastReceiverContext.getInstance().getMediaManager(); // Pass the intent to the SDK. You can also do this in onCreate(). if (mediaManager.onNewIntent(intent)) { // If the SDK recognizes the intent, you should early return. return; } // If the SDK doesn't recognize the intent, you can handle the intent with // your own logic. ... } }
如果MediaManager
會擷取意圖
MediaLoadRequestData
。
物件並叫用
MediaLoadCommandCallback.onLoad()
。
您需要覆寫此方法來處理載入要求。回呼必須
註冊日期早於
MediaManager.onNewIntent()
敬上
呼叫 (建議放置在活動或應用程式 onCreate()
上)
方法)。
class MyActivity : Activity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val mediaManager = CastReceiverContext.getInstance().getMediaManager() mediaManager.setMediaLoadCommandCallback(MyMediaLoadCommandCallback()) } } class MyMediaLoadCommandCallback : MediaLoadCommandCallback() { override fun onLoad( senderId: String?, loadRequestData: MediaLoadRequestData ): Task{ return Tasks.call { // Resolve the entity into your data structure and load media. val mediaInfo = loadRequestData.getMediaInfo() if (!checkMediaInfoSupported(mediaInfo)) { // Throw MediaException to indicate load failure. throw MediaException( MediaError.Builder() .setDetailedErrorCode(DetailedErrorCode.LOAD_FAILED) .setReason(MediaError.ERROR_REASON_INVALID_REQUEST) .build() ) } myFillMediaInfo(MediaInfoWriter(mediaInfo)) myPlayerLoad(mediaInfo.getContentUrl()) // Update media metadata and state (this clears all previous status // overrides). castReceiverContext.getMediaManager() .setDataFromLoad(loadRequestData) ... castReceiverContext.getMediaManager().broadcastMediaStatus() // Return the resolved MediaLoadRequestData to indicate load success. return loadRequestData } } private fun myPlayerLoad(contentURL: String) { myPlayer.load(contentURL) // Update the MediaSession state. val playbackState: PlaybackStateCompat = Builder() .setState( player.getState(), player.getPosition(), System.currentTimeMillis() ) ... .build() mediaSession.setPlaybackState(playbackState) }
public class MyActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); MediaManager mediaManager = CastReceiverContext.getInstance().getMediaManager(); mediaManager.setMediaLoadCommandCallback(new MyMediaLoadCommandCallback()); } } public class MyMediaLoadCommandCallback extends MediaLoadCommandCallback { @Override public TaskonLoad(String senderId, MediaLoadRequestData loadRequestData) { return Tasks.call(() -> { // Resolve the entity into your data structure and load media. MediaInfo mediaInfo = loadRequestData.getMediaInfo(); if (!checkMediaInfoSupported(mediaInfo)) { // Throw MediaException to indicate load failure. throw new MediaException( new MediaError.Builder() .setDetailedErrorCode(DetailedErrorCode.LOAD_FAILED) .setReason(MediaError.ERROR_REASON_INVALID_REQUEST) .build()); } myFillMediaInfo(new MediaInfoWriter(mediaInfo)); myPlayerLoad(mediaInfo.getContentUrl()); // Update media metadata and state (this clears all previous status // overrides). castReceiverContext.getMediaManager() .setDataFromLoad(loadRequestData); ... castReceiverContext.getMediaManager().broadcastMediaStatus(); // Return the resolved MediaLoadRequestData to indicate load success. return loadRequestData; }); } private void myPlayerLoad(String contentURL) { myPlayer.load(contentURL); // Update the MediaSession state. PlaybackStateCompat playbackState = new PlaybackStateCompat.Builder() .setState( player.getState(), player.getPosition(), System.currentTimeMillis()) ... .build(); mediaSession.setPlaybackState(playbackState); }
如要處理載入意圖,您可以將意圖剖析為資料結構
我們定義了
(MediaLoadRequestData
)。
支援媒體指令
支援基本播放控制項
基本整合指令包含與媒體相容的指令 會很有幫助這些指令會透過媒體工作階段回呼收到通知。您需要執行的操作 為了支援這項功能,請註冊媒體工作階段的回呼 (不妨這麼做 )。
private class MyMediaSessionCallback : MediaSessionCompat.Callback() { override fun onPause() { // Pause the player and update the play state. myPlayer.pause() } override fun onPlay() { // Resume the player and update the play state. myPlayer.play() } override fun onSeekTo(pos: Long) { // Seek and update the play state. myPlayer.seekTo(pos) } ... } mediaSession.setCallback(MyMediaSessionCallback())
private class MyMediaSessionCallback extends MediaSessionCompat.Callback { @Override public void onPause() { // Pause the player and update the play state. myPlayer.pause(); } @Override public void onPlay() { // Resume the player and update the play state. myPlayer.play(); } @Override public void onSeekTo(long pos) { // Seek and update the play state. myPlayer.seekTo(pos); } ... } mediaSession.setCallback(new MyMediaSessionCallback());
支援投放控制指令
某些投放指令不支援
MediaSession
、
例如
skipAd()
。
或
setActiveMediaTracks()
。
此外,由於投放佇列,部分佇列指令必須在這裡實作
無法與 MediaSession
佇列完全相容。
class MyMediaCommandCallback : MediaCommandCallback() { override fun onSkipAd(requestData: RequestData?): Task{ // Skip your ad ... return Tasks.forResult(null) } } val mediaManager = CastReceiverContext.getInstance().getMediaManager() mediaManager.setMediaCommandCallback(MyMediaCommandCallback())
public class MyMediaCommandCallback extends MediaCommandCallback { @Override public TaskonSkipAd(RequestData requestData) { // Skip your ad ... return Tasks.forResult(null); } } MediaManager mediaManager = CastReceiverContext.getInstance().getMediaManager(); mediaManager.setMediaCommandCallback(new MyMediaCommandCallback());
指定支援的媒體指令
與 Cast 接收器相同,Android TV 應用程式應指定哪些指令
因此,寄件者可以啟用或停用特定 UI 控制項。適用對象
執行 Pod 中
MediaSession
、
說明如何指定
PlaybackStateCompat
。
請在
MediaStatusModifier
。
// Set media session supported commands val playbackState: PlaybackStateCompat = PlaybackStateCompat.Builder() .setActions(PlaybackStateCompat.ACTION_PLAY or PlaybackStateCompat.ACTION_PAUSE) .setState(PlaybackStateCompat.STATE_PLAYING) .build() mediaSession.setPlaybackState(playbackState) // Set additional commands in MediaStatusModifier val mediaManager = CastReceiverContext.getInstance().getMediaManager() mediaManager.getMediaStatusModifier() .setMediaCommandSupported(MediaStatus.COMMAND_QUEUE_NEXT)
// Set media session supported commands PlaybackStateCompat playbackState = new PlaybackStateCompat.Builder() .setActions(PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PAUSE) .setState(PlaybackStateCompat.STATE_PLAYING) .build(); mediaSession.setPlaybackState(playbackState); // Set additional commands in MediaStatusModifier MediaManager mediaManager = CastReceiverContext.getInstance().getMediaManager(); mediaManager.getMediaStatusModifier() .setMediaCommandSupported(MediaStatus.COMMAND_QUEUE_NEXT);
隱藏不支援的按鈕
如果您的 Android TV 應用程式僅支援基本媒體控制功能,但網路接收器 應用程式支援更進階的控制項,請確定您的寄件者應用程式可正常運作 可正確投放到 Android TV 應用程式例如 Android TV 應用程式不支援在 Web Receiver 應用程式變更播放率的情況下 應該在每個平台上正確設定支援的動作 您的傳送端應用程式會正確顯示 UI。
修改 MediaStatus
如要支援測試群組、廣告、直播和待播清單等進階功能,你的 Android 裝置
TV 應用程式需要提供無法證明的額外資訊
MediaSession
。
我們提供
MediaStatusModifier
敬上
課程。MediaStatusModifier
一律會在
MediaSession
您的目標
CastReceiverContext
。
製作及播送
MediaStatus
:
val mediaManager: MediaManager = castReceiverContext.getMediaManager() val statusModifier: MediaStatusModifier = mediaManager.getMediaStatusModifier() statusModifier .setLiveSeekableRange(seekableRange) .setAdBreakStatus(adBreakStatus) .setCustomData(customData) mediaManager.broadcastMediaStatus()
MediaManager mediaManager = castReceiverContext.getMediaManager(); MediaStatusModifier statusModifier = mediaManager.getMediaStatusModifier(); statusModifier .setLiveSeekableRange(seekableRange) .setAdBreakStatus(adBreakStatus) .setCustomData(customData); mediaManager.broadcastMediaStatus();
我們的用戶端程式庫會從 MediaSession
取得基礎 MediaStatus
,
Android TV 應用程式可以透過
MediaStatus
修飾符。
某些狀態和中繼資料可在 MediaSession
和
MediaStatusModifier
。我們強烈建議您僅設定在
MediaSession
。您仍然可以使用修飾符覆寫
MediaSession
:不建議使用,因為修飾符中的狀態一律會
優先順序高於 MediaSession
提供的值。
在送出前攔截 MediaStatus
與 Web Receiver SDK 相同,如果您想在
您可以指定
MediaStatusInterceptor
敬上
處理
MediaStatus
變更為
來產生。將
MediaStatusWriter
敬上
在傳送 MediaStatus
前加以操控
mediaManager.setMediaStatusInterceptor(object : MediaStatusInterceptor { override fun intercept(mediaStatusWriter: MediaStatusWriter) { // Perform customization. mediaStatusWriter.setCustomData(JSONObject("{data: \"my Hello\"}")) } })
mediaManager.setMediaStatusInterceptor(new MediaStatusInterceptor() { @Override public void intercept(MediaStatusWriter mediaStatusWriter) { // Perform customization. mediaStatusWriter.setCustomData(new JSONObject("{data: \"my Hello\"}")); } });
處理使用者憑證
您的 Android TV 應用程式可能只允許特定使用者啟動或加入應用程式 會很有幫助舉例來說,僅允許寄件者啟動或加入:
- 傳送者應用程式所登入的帳戶和個人資料與 ATV 應用程式相同。
- 傳送者應用程式已登入同一個帳戶,但個人資料與 ATV 應用程式不同。
如果您的應用程式可以處理多位使用者或匿名使用者,您可以允許其他 使用者加入 ATV 工作階段。如果使用者提供憑證,您的 ATV 應用程式 必須處理自己的憑證 以便擷取進度和其他使用者資料 正確追蹤
傳送者應用程式啟動或加入 Android TV 應用程式時,你的傳送端應用程式 請提供代表加入工作階段的使用者憑證。
在傳送者啟動並加入 Android TV 應用程式之前,你可以指定 啟動檢查工具,查看是否允許傳送者憑證。如果不是,「投放」 Connect SDK 即將改回啟動 Web 接收器。
寄件者應用程式啟動憑證資料
在傳送者端,您可以指定 CredentialsData
來代表
加入課程
credentials
是可以由使用者定義的字串,可以是您的 ATV
應用程式就能理解credentialsType
會定義
CredentialsData
來自或可以自訂的值。預設設定為
指向流量的來源平台
CredentialsData
只會在啟動期間傳遞至您的 Android TV 應用程式,或是
加入時間。如果在連線時再次設定 PIN 碼,系統不會將密碼傳遞至
Android TV 應用程式。如果寄件者在連線時切換設定檔,
可以留在工作階段中
SessionManager.endCurrentCastSession(boolean stopCasting)
敬上
如果您認為新的設定檔與工作階段不相容。
CredentialsData
敬上
則可使用
getSenders
。
的
CastReceiverContext
取得 SenderInfo
,
getCastLaunchRequest()
來取得
CastLaunchRequest
、
下一步
getCredentialsData()
。
需要 play-services-cast-framework
版本
19.0.0
以上版本。
CastContext.getSharedInstance().setLaunchCredentialsData( CredentialsData.Builder() .setCredentials("{\"userId\": \"abc\"}") .build() )
CastContext.getSharedInstance().setLaunchCredentialsData( new CredentialsData.Builder() .setCredentials("{\"userId\": \"abc\"}") .build());
需要 google-cast-sdk
v4.8.3
或
更高。
設定選項之後,隨時都可以呼叫:
GCKCastContext.setSharedInstanceWith(options)
。
GCKCastContext.sharedInstance().setLaunch( GCKCredentialsData(credentials: "{\"userId\": \"abc\"}")
需使用 Chromium 瀏覽器版本
M87
以上版本。
設定選項之後,隨時都可以呼叫:
cast.framework.CastContext.getInstance().setOptions(options);
。
let credentialsData = new chrome.cast.CredentialsData("{\"userId\": \"abc\"}"); cast.framework.CastContext.getInstance().setLaunchCredentialsData(credentialsData);
實作 ATV 啟動要求檢查工具
CredentialsData
敬上
在傳送者嘗試啟動或加入時,傳遞至你的 Android TV 應用程式。你可以
會執行
LaunchRequestChecker
。
決定要允許或拒絕這項要求
如果要求遭拒,則會載入網路接收端,而不是啟動 內建到 ATV 應用程式中如果 ATV 無法成功,請拒絕要求 處理要求啟動或加入的使用者。舉例來說 使用者的登入 ATV 應用程式超過請求,而您的應用程式無法 或是目前沒有使用者登入 ATV 應用程式。
如果允許請求,ATV 應用程式就會啟動。您可以自訂這項設定
取決於應用程式是否支援傳送載入要求
未登入 ATV 應用程式,或其中使用者不符。這項行為是
LaunchRequestChecker
中可完全自訂。
建立實作
CastReceiverOptions.LaunchRequestChecker
敬上
介面:
class MyLaunchRequestChecker : LaunchRequestChecker { override fun checkLaunchRequestSupported(launchRequest: CastLaunchRequest): Task{ return Tasks.call { myCheckLaunchRequest( launchRequest ) } } } private fun myCheckLaunchRequest(launchRequest: CastLaunchRequest): Boolean { val credentialsData = launchRequest.getCredentialsData() ?: return false // or true if you allow anonymous users to join. // The request comes from a mobile device, e.g. checking user match. return if (credentialsData.credentialsType == CredentialsData.CREDENTIALS_TYPE_ANDROID) { myCheckMobileCredentialsAllowed(credentialsData.getCredentials()) } else false // Unrecognized credentials type. }
public class MyLaunchRequestChecker implements CastReceiverOptions.LaunchRequestChecker { @Override public TaskcheckLaunchRequestSupported(CastLaunchRequest launchRequest) { return Tasks.call(() -> myCheckLaunchRequest(launchRequest)); } } private boolean myCheckLaunchRequest(CastLaunchRequest launchRequest) { CredentialsData credentialsData = launchRequest.getCredentialsData(); if (credentialsData == null) { return false; // or true if you allow anonymous users to join. } // The request comes from a mobile device, e.g. checking user match. if (credentialsData.getCredentialsType().equals(CredentialsData.CREDENTIALS_TYPE_ANDROID)) { return myCheckMobileCredentialsAllowed(credentialsData.getCredentials()); } // Unrecognized credentials type. return false; }
然後在以下項目中設定:
ReceiverOptionsProvider
:
class MyReceiverOptionsProvider : ReceiverOptionsProvider { override fun getOptions(context: Context?): CastReceiverOptions { return CastReceiverOptions.Builder(context) ... .setLaunchRequestChecker(MyLaunchRequestChecker()) .build() } }
public class MyReceiverOptionsProvider implements ReceiverOptionsProvider { @Override public CastReceiverOptions getOptions(Context context) { return new CastReceiverOptions.Builder(context) ... .setLaunchRequestChecker(new MyLaunchRequestChecker()) .build(); } }
解決 true
的
LaunchRequestChecker
啟動 ATV 應用程式,false
則會啟動 Web Receiver 應用程式。
傳送與接收自訂訊息
Cast 通訊協定可讓您在寄件者和傳送者之間傳送自訂字串訊息
接收端應用程式。您必須註冊命名空間 (頻道) 才能傳送
才能初始化
CastReceiverContext
。
Android TV:指定自訂命名空間
您需要在
CastReceiverOptions
敬上
設定期間:
class MyReceiverOptionsProvider : ReceiverOptionsProvider { override fun getOptions(context: Context?): CastReceiverOptions { return CastReceiverOptions.Builder(context) .setCustomNamespaces( Arrays.asList("urn:x-cast:com.example.cast.mynamespace") ) .build() } }
public class MyReceiverOptionsProvider implements ReceiverOptionsProvider { @Override public CastReceiverOptions getOptions(Context context) { return new CastReceiverOptions.Builder(context) .setCustomNamespaces( Arrays.asList("urn:x-cast:com.example.cast.mynamespace")) .build(); } }
Android TV - 傳送訊息
// If senderId is null, then the message is broadcasted to all senders. CastReceiverContext.getInstance().sendMessage( "urn:x-cast:com.example.cast.mynamespace", senderId, customString)
// If senderId is null, then the message is broadcasted to all senders. CastReceiverContext.getInstance().sendMessage( "urn:x-cast:com.example.cast.mynamespace", senderId, customString);
Android TV - 接收自訂命名空間訊息
class MyCustomMessageListener : MessageReceivedListener { override fun onMessageReceived( namespace: String, senderId: String?, message: String ) { ... } } CastReceiverContext.getInstance().setMessageReceivedListener( "urn:x-cast:com.example.cast.mynamespace", new MyCustomMessageListener());
class MyCustomMessageListener implements CastReceiverContext.MessageReceivedListener { @Override public void onMessageReceived( String namespace, String senderId, String message) { ... } } CastReceiverContext.getInstance().setMessageReceivedListener( "urn:x-cast:com.example.cast.mynamespace", new MyCustomMessageListener());