Android TV 수신기에 핵심 기능 추가

컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요.

이 페이지에는 Android TV 수신기 앱을 맞춤설정하는 데 사용할 수 있는 기능에 관한 코드 스니펫과 설명이 포함되어 있습니다.

라이브러리 구성

Cast Connect API를 Android TV 앱에서 사용할 수 있도록 하려면 다음 단계를 따르세요.

Android
  1. 애플리케이션 모듈 디렉터리 내의 build.gradle 파일을 엽니다.
  2. google()이 나열된 repositories에 포함되어 있는지 확인합니다.
      repositories {
        google()
      }
  3. 앱의 타겟 기기 유형에 따라 종속 항목의 최신 버전을 추가합니다.
    • Android 수신기 앱:
        dependencies {
          implementation 'com.google.android.gms:play-services-cast-tv:20.0.0'
          implementation 'com.google.android.gms:play-services-cast:21.2.0'
        }
    • Android Sender 앱:
        dependencies {
          implementation 'com.google.android.gms:play-services-cast:20.0.0'
          implementation 'com.google.android.gms:play-services-cast-framework:21.2.0'
        }
    서비스가 업데이트될 때마다 이 버전 번호를 업데이트해야 합니다.
  4. 변경사항을 저장하고 툴바에서 Sync Project with Gradle Files를 클릭합니다.
iOS
  1. Podfile에서 google-cast-sdk 4.7.0 이상을 타겟팅해야 합니다.
  2. iOS 12 이상을 타겟팅합니다. 자세한 내용은 출시 노트를 참고하세요.
      platform: ios, '12'
    
      def target_pods
         pod 'google-cast-sdk', '~>4.7.0'
      end
  1. Chromium 브라우저 버전 M87 이상이 필요합니다.
  2.   <script src="//www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1"></script>
    프로젝트에 Web Sender API 라이브러리 추가

AndroidX 요구사항

Google Play 서비스 새 버전에서는 androidx 네임스페이스를 사용하도록 앱을 업데이트해야 합니다. 안내에 따라 AndroidX로 이전합니다.

Android TV 앱 - 기본 요건

Android TV 앱에서 Cast Connect를 지원하려면 미디어 세션에서 이벤트를 만들고 지원해야 합니다. 미디어 세션에서 제공하는 데이터는 미디어 상태에 대한 기본 정보(예: 위치, 재생 상태 등)를 제공합니다. 미디어 세션은 Cast Connect 라이브러리에서도 발신자와 같은 특정 메시지를 일시중지할 때 신호를 보내는 데 사용됩니다.

미디어 세션과 미디어 세션 초기화 방법에 관한 자세한 내용은 미디어 세션 작업 가이드를 참고하세요.

미디어 세션 수명 주기

앱은 재생이 시작되면 미디어 세션을 만들고 더 이상 제어할 수 없는 경우 해제해야 합니다. 예를 들어 동영상 앱인 경우 다른 콘텐츠를 찾아보도록 '뒤로'를 선택하거나 앱을 백그라운드로 설정하여 사용자가 재생 활동을 종료할 때 세션을 해제해야 합니다. 앱이 음악 앱인 경우 앱에서 더 이상 미디어를 재생하지 않을 때 해제해야 합니다.

세션 상태 업데이트

미디어 세션의 데이터는 플레이어의 상태와 최신 상태로 유지되어야 합니다. 예를 들어 재생이 일시중지되면 재생 상태 및 지원되는 작업을 업데이트해야 합니다. 다음 표에는 사용자가 최신 상태를 유지해야 하는 상태가 나열되어 있습니다.

MediaMetadataCompat와 함께 사용하면 됩니다.

메타데이터 필드 설명
METADATA_KEY_TITLE(필수) 미디어 제목입니다.
METADATA_KEY_DISPLAY_SUBTITLE 부제목입니다.
메타데이터_키_디스플레이_아이콘_URI 아이콘 URL입니다.
METADATA_KEY_DURATION(필수) 미디어 재생 시간.
메타데이터_키_미디어_URI Content ID
메타데이터_키_아티스트 아티스트
메타데이터_키_앨범 앨범

PlaybackStateCompat

필수 메서드 설명
SetActions() 지원되는 미디어 명령어를 설정합니다.
State 재생 상태와 현재 위치를 설정합니다.

MediaSessionCompat

필수 메서드 설명
RepeatRepeat 반복 모드를 설정합니다.
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)
}
자바
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() );
자바
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 지원 구성

발신기 애플리케이션이 시작 요청을 전송하면 애플리케이션 네임스페이스가 포함된 인텐트가 생성됩니다. 애플리케이션은 이를 처리하고 TV 앱이 실행될 때 CastReceiverContext 객체의 인스턴스를 생성합니다. CastReceiverContext 객체는 TV 앱이 실행되는 동안 Cast와 상호작용하기 위해 필요합니다. 이 객체를 사용하면 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>

수신기 옵션 제공자 지정

CastReceiverOptions를 제공하려면 ReceiverOptionsProvider를 구현해야 합니다.

Kotlin
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" />

ReceiverOptionsProviderCastReceiverContext가 초기화될 때 CastReceiverOptions를 제공하는 데 사용됩니다.

Cast 수신기 컨텍스트

앱을 만들 때 CastReceiverContext를 초기화합니다.

Kotlin
override fun onCreate() {
  CastReceiverContext.initInstance(this)

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

  ...
}

앱이 포그라운드로 전환될 때 CastReceiverContext를 시작합니다.

Kotlin
CastReceiverContext.getInstance().start()
자바
CastReceiverContext.getInstance().start();

백그라운드 동영상 재생을 지원하지 않는 동영상 앱 또는 앱의 백그라운드 앱으로 이동한 후 CastReceiverContext에서 stop()를 호출합니다.

Kotlin
// Player has stopped.
CastReceiverContext.getInstance().stop()
자바
// Player has stopped.
CastReceiverContext.getInstance().stop();

또한 앱이 백그라운드에서 재생을 지원하는 경우 백그라운드에서 재생을 중지하면 CastReceiverContext에서 stop()를 호출합니다.

특히 네이티브 앱에 여러 활동이 있는 경우 androidx.lifecycle 라이브러리의 LifecycleObserver를 사용하여 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())
  }
}
자바
// 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">

MediaManager에 MediaSession 연결

MediaSession를 만들 때 현재 MediaSession 토큰도 CastReceiverContext에 제공하여 명령어를 전송할 위치와 미디어 재생 상태를 검색할 위치를 알 수 있도록 해야 합니다.

Kotlin
val mediaManager: MediaManager = receiverContext.getMediaManager()
mediaManager.setSessionCompatToken(currentMediaSession.getSessionToken())
자바
MediaManager mediaManager = receiverContext.getMediaManager();
mediaManager.setSessionCompatToken(currentMediaSession.getSessionToken());

비활성 재생으로 인해 MediaSession를 해제하는 경우 MediaManager에 null 토큰을 설정해야 합니다.

Kotlin
myPlayer.stop()
mediaSession.release()
mediaManager.setSessionCompatToken(null)
자바
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()
  }
}
자바
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.MediaButtonEventHandlerMediaSessionCompat.Callback에 의해 기본적으로 처리되는 setMediaButtonEventHandler(MediaButtonEventHandler)를 호출하여 MediaButton 이벤트를 처리하는 데 사용할 수 있습니다.

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)
    ...
  }
}
자바
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 지원으로 발신기 앱을 업데이트한 후 LaunchOptions에서 androidReceiverCompatible 플래그를 true로 설정하여 준비 상태를 선언할 수 있습니다.

Android

play-services-cast-framework 버전 19.0.0 이상이 필요합니다.

androidReceiverCompatible 플래그는 CastOptions의 일부인 LaunchOptions에 설정됩니다.

Kotlin
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();
  }
}
iOS

google-cast-sdk 버전 v4.4.8 이상이 필요합니다.

androidReceiverCompatible 플래그는 GCKCastOptions의 일부인 GCKLaunchOptions에 설정됩니다.

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 앱 구성

Cast 개발자 콘솔에 Android TV 앱의 패키지 이름을 추가하여 Cast 앱 ID와 연결합니다.

개발자 기기 등록

Cast 개발자 콘솔에서 개발에 사용할 Android TV 기기의 일련번호를 등록합니다.

등록하지 않으면 보안상의 이유로 Cast Connect가 Google Play 스토어에서 설치된 앱에서만 작동합니다.

Cast 개발을 위한 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
자바
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 사용자 인증 정보 설정

웹 수신기 앱과 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)
자바
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);

웹 수신기 앱이 실행되면 로드 요청에서 entitycredentials를 사용합니다. 하지만 Android TV 앱이 실행되면 SDK는 entitycredentialsatvEntityatvCredentials(지정된 경우)로 재정의합니다.

Content ID 또는 MediaQueueData로 로드

entityatvEntity를 사용하지 않고 미디어 정보에서 Content ID 또는 콘텐츠 URL을 사용하거나 더 자세한 미디어 로드 요청 데이터를 사용하는 경우 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()를 호출할 수 있습니다.

Android
Kotlin
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);
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.
    ...
  }
}
자바
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)
  }
자바
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())
자바
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());

Cast 제어 명령어 지원

skipAd() 또는 setActiveMediaTracks()와 같이 MediaSession에서 사용할 수 없는 Cast 명령어가 있습니다. 또한 Cast 큐가 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())
자바
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 컨트롤을 사용 설정하거나 중지할 수 있습니다. 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)
자바
// 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 앱이 지원하는 동안 재생 속도 변경을 지원하지 않는다면 각 플랫폼에서 지원되는 작업을 올바르게 설정하고 발신기 앱이 UI를 올바르게 렌더링하는지 확인해야 합니다.

MediaStatus 수정

트랙, 광고, 실시간, 대기열과 같은 고급 기능을 지원하려면 Android TV 앱에서 MediaSession를 통해 확인할 수 없는 추가 정보를 제공해야 합니다.

이를 위해 MediaStatusModifier 클래스를 제공합니다. MediaStatusModifier는 항상 CastReceiverContext에서 설정한 MediaSession에서 작동합니다.

MediaStatus를 만들고 브로드캐스트하려면 다음 안내를 따르세요.

Kotlin
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 수정자를 통해 추가 상태를 지정하고 상태를 재정의할 수 있습니다.

일부 상태와 메타데이터는 MediaSessionMediaStatusModifier에서 모두 설정할 수 있습니다. MediaSession에서만 설정하는 것이 좋습니다. 여전히 수정자를 사용하여 MediaSession의 상태를 재정의할 수 있습니다. 이 경우 권장되지 않습니다. 이는 MediaSession에서 제공하는 값보다 항상 수정자의 상태가 더 높기 때문입니다.

전송하기 전에 MediaStatus 가로채기

웹 수신기 SDK와 마찬가지로 보내기 전에 몇 가지 마무리 작업을 하려면 MediaStatusInterceptor을 지정하여 전송할 MediaStatus를 처리하면 됩니다. MediaStatusWriter을 전달하기 전에 MediaStatus을(를) 전달합니다.

Kotlin
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 앱에 참여할 때 발신기 앱은 세션에 참여 중인 사용자를 나타내는 사용자 인증 정보를 제공해야 합니다.

발신자가 Android TV 앱을 실행하고 참여하기 전에 실행 검사기를 지정하여 발신자 사용자 인증 정보가 허용되는지 확인할 수 있습니다. 그렇지 않은 경우 Cast Connect SDK가 웹 수신기를 실행합니다.

발신기 앱 실행 사용자 인증 정보 데이터

발신자 측에서는 CredentialsData를 지정하여 세션에 참여하는 사용자를 나타낼 수 있습니다.

credentials는 ATV 앱에서 인식할 수 있는 한 사용자가 정의할 수 있는 문자열입니다. credentialsTypeCredentialsData가 제공하는 플랫폼을 정의하거나 맞춤 값이 될 수 있습니다. 기본적으로 이 데이터가 발신되는 플랫폼으로 설정됩니다.

CredentialsData는 실행 또는 참여 시간에만 Android TV 앱에 전달됩니다. 연결된 상태에서 다시 설정하면 Android TV 앱으로 전달되지 않습니다. 발신자가 연결 중에 프로필을 전환하면 세션에 남아 있거나 새 프로필이 세션과 호환되지 않는다고 생각하는 경우 SessionManager.endCurrentCastSession(boolean stopCasting)를 호출할 수 있습니다.

각 발신자의 CredentialsDataCastReceiverContext에서 getSenders를 사용하여 SenderInfo를 가져오고 getCastLaunchRequest()를 사용하여 CastLaunchRequest을 가져오고 getCredentialsData()을 가져올 수 있습니다.

Android

play-services-cast-framework 버전 19.0.0 이상이 필요합니다.

Kotlin
CastContext.getSharedInstance().setLaunchCredentialsData(
    CredentialsData.Builder()
        .setCredentials("{\"userId\": \"abc\"}")
        .build()
)
자바
CastContext.getSharedInstance().setLaunchCredentialsData(
    new CredentialsData.Builder()
        .setCredentials("{\"userId\": \"abc\"}")
        .build());
iOS

google-cast-sdk 버전 v4.7.0 이상이 필요합니다.

옵션이 설정된 후 언제든지 호출할 수 있습니다. 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.
}
자바
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()
  }
}
자바
public class MyReceiverOptionsProvider implements ReceiverOptionsProvider {
  @Override
  public CastReceiverOptions getOptions(Context context) {
    return new CastReceiverOptions.Builder(context)
        ...
        .setLaunchRequestChecker(new MyLaunchRequestChecker())
        .build();
  }
}

LaunchRequestChecker에서 true를 확인하면 ATV 앱이 실행되고 false는 웹 수신기 앱을 실행합니다.

맞춤 메시지 주고받기

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()
  }
}
자바
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)
자바
// 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());
자바
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());