為 Android TV 接收器新增核心功能

此網頁包含程式碼片段和其可用功能的說明 自訂 Android TV 接收器應用程式。

設定程式庫

如要為 Android TV 應用程式提供 Cast Connect API,請按照下列步驟操作:

Android 版
  1. 在應用程式模組目錄中開啟 build.gradle 檔案。
  2. 確認 google() 已包含在列出的 repositories 中。
      repositories {
        google()
      }
  3. 依據應用程式的目標裝置類型,加入最新版本 將程式庫部署至依附元件:
    • 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'
        }
    ,瞭解如何調查及移除這項存取權。 每次更新服務時,請務必更新這組版本號碼。
  4. 儲存變更,然後按一下 Sync Project with Gradle Files 點選工具列中的「校對這份文件」
,瞭解如何調查及移除這項存取權。
iOS
  1. 請確定您的 Podfile 指定 google-cast-sdk 4.8.3 或更高
  2. 指定 iOS 14 以上版本。請參閱版本資訊 ,掌握更多詳細資訊。
      platform: ios, '14'
    
      def target_pods
         pod 'google-cast-sdk', '~>4.8.3'
      end
,瞭解如何調查及移除這項存取權。
網頁
  1. 需要 Chromium 瀏覽器 M87 以上版本。
  2. 將 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() 設定播放狀態。
Kotlin
,瞭解如何調查及移除這項存取權。
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)
}
Java
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() 停止目前的媒體
Kotlin
,瞭解如何調查及移除這項存取權。
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() );
Java
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:

Kotlin
,瞭解如何調查及移除這項存取權。
class MyReceiverOptionsProvider : ReceiverOptionsProvider {
  override fun getOptions(context: Context?): CastReceiverOptions {
    return CastReceiverOptions.Builder(context)
          .setStatusText("My App")
          .build()
    }
}
Java
,瞭解如何調查及移除這項存取權。
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 會在下列情況用於提供 CastReceiverOptionsCastReceiverContext 已初始化。

投放接收器內容

CastReceiverContext敬上 建立應用程式時:

Kotlin
,瞭解如何調查及移除這項存取權。
override fun onCreate() {
  CastReceiverContext.initInstance(this)

  ...
}
Java
@Override
public void onCreate() {
  CastReceiverContext.initInstance(this);

  ...
}

在應用程式移至前景時啟動 CastReceiverContext

Kotlin
,瞭解如何調查及移除這項存取權。
CastReceiverContext.getInstance().start()
Java
CastReceiverContext.getInstance().start();

致電 stop()敬上 的 CastReceiverContext 如果影片應用程式進入背景,不支援不支援的應用程式 背景播放:

Kotlin
,瞭解如何調查及移除這項存取權。
// Player has stopped.
CastReceiverContext.getInstance().stop()
Java
// Player has stopped.
CastReceiverContext.getInstance().stop();

此外,如果您的應用程式支援在背景播放,請呼叫 stop()CastReceiverContext 於背景停止播放時。

我們強烈建議您從 androidx.lifecycle敬上 管理通話的程式庫 CastReceiverContext.start()CastReceiverContext.stop()、 尤其是原生應用程式有多個活動時這可避免發生競速 在不同活動中呼叫 start()stop() 時的情況。

Kotlin
,瞭解如何調查及移除這項存取權。
// 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())
  }
}
Java
// 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 以便知道要在哪裡傳送指令並擷取媒體播放狀態:

Kotlin
,瞭解如何調查及移除這項存取權。
val mediaManager: MediaManager = receiverContext.getMediaManager()
mediaManager.setSessionCompatToken(currentMediaSession.getSessionToken())
Java
MediaManager mediaManager = receiverContext.getMediaManager();
mediaManager.setSessionCompatToken(currentMediaSession.getSessionToken());

當你因為播放無效而釋出MediaSession時,應設定 出現空值權杖 MediaManager:

Kotlin
,瞭解如何調查及移除這項存取權。
myPlayer.stop()
mediaSession.release()
mediaManager.setSessionCompatToken(null)
Java
myPlayer.stop();
mediaSession.release();
mediaManager.setSessionCompatToken(null);

如果您的應用程式可在背景執行時播放媒體,請 通話 CastReceiverContext.stop()敬上 應用程式傳送至背景時,只應在應用程式運作時呼叫 並在背景繼續播放媒體。例如:

Kotlin
,瞭解如何調查及移除這項存取權。
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()
  }
}
Java
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敬上 ,請將下列內容新增到您的玩家活動類別,或您處的任何地方 管理媒體工作階段:

Kotlin
,瞭解如何調查及移除這項存取權。
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)
    ...
  }
}
Java
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

Android 版

需要 play-services-cast-framework 版本 19.0.0 以上版本。

androidReceiverCompatible 旗標位於 LaunchOptions (屬於 CastOptions 的一部分):

Kotlin
,瞭解如何調查及移除這項存取權。
class CastOptionsProvider : OptionsProvider {
  override fun getCastOptions(context: Context?): CastOptions {
    val launchOptions: LaunchOptions = Builder()
          .setAndroidReceiverCompatible(true)
          .build()
    return CastOptions.Builder()
          .setLaunchOptions(launchOptions)
          ...
          .build()
    }
}
Java
,瞭解如何調查及移除這項存取權。
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();
  }
}
iOS

需要 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,藉此傳遞深層連結。 載入請求相關資訊:

Kotlin
,瞭解如何調查及移除這項存取權。
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)
Android
,瞭解如何調查及移除這項存取權。
Java
,瞭解如何調查及移除這項存取權。
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);
iOS
,瞭解如何調查及移除這項存取權。
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);

系統會使用含有深層連結和套件名稱的意圖,傳送載入指令 所定義的名稱

為傳送者設定 ATV 憑證

您的 Web Receiver 應用程式和 Android TV 應用程式支援可能的情況有所不同 深層連結和 credentials (例如處理驗證時) 兩個平台不同)。如要解決這個問題,您可以提供 Android TV 的 entitycredentials

Android 版
,瞭解如何調查及移除這項存取權。
Kotlin
,瞭解如何調查及移除這項存取權。
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)
Java
,瞭解如何調查及移除這項存取權。
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);
iOS
,瞭解如何調查及移除這項存取權。
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 應用程式啟動後,則會使用應用程式中的 entitycredentials 載入要求。不過,如果您的 Android TV 應用程式已啟動,SDK 會覆寫 相關entitycredentials (包含您的atvEntityatvCredentials) (如有指定)。

依 Content ID 或 MediaQueueData 載入

如未使用 entityatvEntity,且使用 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()

Android 版
,瞭解如何調查及移除這項存取權。
Kotlin
,瞭解如何調查及移除這項存取權。
val mediaToLoad = MediaInfo.Builder("some-id").build()
val loadRequest = MediaLoadRequestData.Builder()
    .setMediaInfo(mediaToLoad)
    .setCredentials("user-credentials")
    ...
    .build()
remoteMediaClient.load(loadRequest)
Java
,瞭解如何調查及移除這項存取權。
MediaInfo mediaToLoad =
    new MediaInfo.Builder("some-id").build();
MediaLoadRequestData loadRequest =
    new MediaLoadRequestData.Builder()
        .setMediaInfo(mediaToLoad)
        .setCredentials("user-credentials")
        ...
        .build();
remoteMediaClient.load(loadRequest);
iOS
,瞭解如何調查及移除這項存取權。
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);

處理載入要求

如要在活動中處理這些載入要求,您需要處理意圖 呼叫事件:

Kotlin
,瞭解如何調查及移除這項存取權。
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.
    ...
  }
}
Java
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() 上) 方法)。

Kotlin
,瞭解如何調查及移除這項存取權。
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)
  }
Java
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 Task onLoad(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)。 )。

支援媒體指令

支援基本播放控制項

基本整合指令包含與媒體相容的指令 會很有幫助這些指令會透過媒體工作階段回呼收到通知。您需要執行的操作 為了支援這項功能,請註冊媒體工作階段的回呼 (不妨這麼做 )。

Kotlin
,瞭解如何調查及移除這項存取權。
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())
Java
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 佇列完全相容。

Kotlin
,瞭解如何調查及移除這項存取權。
class MyMediaCommandCallback : MediaCommandCallback() {
    override fun onSkipAd(requestData: RequestData?): Task {
        // Skip your ad
        ...
        return Tasks.forResult(null)
    }
}

val mediaManager = CastReceiverContext.getInstance().getMediaManager()
mediaManager.setMediaCommandCallback(MyMediaCommandCallback())
Java
public class MyMediaCommandCallback extends MediaCommandCallback {
  @Override
  public Task onSkipAd(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

Kotlin
,瞭解如何調查及移除這項存取權。
// 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)
Java
// 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:

Kotlin
,瞭解如何調查及移除這項存取權。
val mediaManager: MediaManager = castReceiverContext.getMediaManager()
val statusModifier: MediaStatusModifier = mediaManager.getMediaStatusModifier()

statusModifier
    .setLiveSeekableRange(seekableRange)
    .setAdBreakStatus(adBreakStatus)
    .setCustomData(customData)

mediaManager.broadcastMediaStatus()
Java
MediaManager mediaManager = castReceiverContext.getMediaManager();
MediaStatusModifier statusModifier = mediaManager.getMediaStatusModifier();

statusModifier
    .setLiveSeekableRange(seekableRange)
    .setAdBreakStatus(adBreakStatus)
    .setCustomData(customData);

mediaManager.broadcastMediaStatus();

我們的用戶端程式庫會從 MediaSession 取得基礎 MediaStatus, Android TV 應用程式可以透過 MediaStatus 修飾符。

某些狀態和中繼資料可在 MediaSessionMediaStatusModifier。我們強烈建議您僅設定在 MediaSession。您仍然可以使用修飾符覆寫 MediaSession:不建議使用,因為修飾符中的狀態一律會 優先順序高於 MediaSession 提供的值。

在送出前攔截 MediaStatus

與 Web Receiver SDK 相同,如果您想在 您可以指定 MediaStatusInterceptor敬上 處理 MediaStatus 變更為 來產生。將 MediaStatusWriter敬上 在傳送 MediaStatus 前加以操控

Kotlin
,瞭解如何調查及移除這項存取權。
mediaManager.setMediaStatusInterceptor(object : MediaStatusInterceptor {
    override fun intercept(mediaStatusWriter: MediaStatusWriter) {
      // Perform customization.
        mediaStatusWriter.setCustomData(JSONObject("{data: \"my Hello\"}"))
    }
})
Java
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敬上 則可使用 getSendersCastReceiverContext 取得 SenderInfogetCastLaunchRequest() 來取得 CastLaunchRequest, 下一步 getCredentialsData()

Android 版

需要 play-services-cast-framework 版本 19.0.0 以上版本。

Kotlin
,瞭解如何調查及移除這項存取權。
CastContext.getSharedInstance().setLaunchCredentialsData(
    CredentialsData.Builder()
        .setCredentials("{\"userId\": \"abc\"}")
        .build()
)
Java
,瞭解如何調查及移除這項存取權。
CastContext.getSharedInstance().setLaunchCredentialsData(
    new CredentialsData.Builder()
        .setCredentials("{\"userId\": \"abc\"}")
        .build());
iOS

需要 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敬上 介面:

Kotlin
,瞭解如何調查及移除這項存取權。
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.
}
Java
public class MyLaunchRequestChecker
    implements CastReceiverOptions.LaunchRequestChecker {
  @Override
  public Task checkLaunchRequestSupported(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:

Kotlin
,瞭解如何調查及移除這項存取權。
class MyReceiverOptionsProvider : ReceiverOptionsProvider {
  override fun getOptions(context: Context?): CastReceiverOptions {
    return CastReceiverOptions.Builder(context)
        ...
        .setLaunchRequestChecker(MyLaunchRequestChecker())
        .build()
  }
}
Java
public class MyReceiverOptionsProvider implements ReceiverOptionsProvider {
  @Override
  public CastReceiverOptions getOptions(Context context) {
    return new CastReceiverOptions.Builder(context)
        ...
        .setLaunchRequestChecker(new MyLaunchRequestChecker())
        .build();
  }
}

解決 trueLaunchRequestChecker 啟動 ATV 應用程式,false 則會啟動 Web Receiver 應用程式。

傳送與接收自訂訊息

Cast 通訊協定可讓您在寄件者和傳送者之間傳送自訂字串訊息 接收端應用程式。您必須註冊命名空間 (頻道) 才能傳送 才能初始化 CastReceiverContext

Android TV:指定自訂命名空間

您需要在 CastReceiverOptions敬上 設定期間:

Kotlin
,瞭解如何調查及移除這項存取權。
class MyReceiverOptionsProvider : ReceiverOptionsProvider {
  override fun getOptions(context: Context?): CastReceiverOptions {
    return CastReceiverOptions.Builder(context)
        .setCustomNamespaces(
            Arrays.asList("urn:x-cast:com.example.cast.mynamespace")
        )
        .build()
  }
}
Java
,瞭解如何調查及移除這項存取權。
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 - 傳送訊息

Kotlin
,瞭解如何調查及移除這項存取權。
// If senderId is null, then the message is broadcasted to all senders.
CastReceiverContext.getInstance().sendMessage(
    "urn:x-cast:com.example.cast.mynamespace", senderId, customString)
Java
// 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 - 接收自訂命名空間訊息

Kotlin
,瞭解如何調查及移除這項存取權。
class MyCustomMessageListener : MessageReceivedListener {
    override fun onMessageReceived(
        namespace: String, senderId: String?, message: String ) {
        ...
    }
}

CastReceiverContext.getInstance().setMessageReceivedListener(
    "urn:x-cast:com.example.cast.mynamespace", new MyCustomMessageListener());
Java
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());