Diese Seite enthält Code-Snippets und Beschreibungen der Funktionen, die zum Anpassen einer Android TV-Receiver-App verfügbar sind.
Bibliotheken konfigurieren
So machen Sie Cast Connect APIs für Ihre Android TV App verfügbar:
-
Öffnen Sie die Datei
build.gradle
im Verzeichnis Ihres Anwendungsmoduls. -
Prüfe, ob
google()
im aufgeführtenrepositories
enthalten ist.repositories { google() }
-
Fügen Sie abhängig vom Zielgerätetyp Ihrer App den Abhängigkeiten die neuesten Versionen der Bibliotheken hinzu:
-
Für Android Receiver App:
dependencies { implementation 'com.google.android.gms:play-services-cast-tv:21.1.0' implementation 'com.google.android.gms:play-services-cast:21.5.0' }
-
Für Android Sender-App:
dependencies { implementation 'com.google.android.gms:play-services-cast:21.1.0' implementation 'com.google.android.gms:play-services-cast-framework:21.5.0' }
-
Für Android Receiver App:
-
Speichern Sie die Änderungen und klicken Sie in der Symbolleiste auf
Sync Project with Gradle Files
.
-
Podfile
muss aufgoogle-cast-sdk
4.8.1 oder höher ausgerichtet sein. -
Richten Sie Ihre App auf iOS 14 oder höher aus. Weitere Informationen finden Sie in den Versionshinweisen.
platform: ios, '14' def target_pods pod 'google-cast-sdk', '~>4.8.1' end
- Erfordert die Chromium-Browserversion M87 oder höher.
-
Fügen Sie Ihrem Projekt
<script src="//www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1"></script>
die Web Sender API-Bibliothek hinzu
AndroidX-Anforderung
Für neue Versionen der Google Play-Dienste muss eine App aktualisiert werden, um den Namespace androidx
zu verwenden. Folgen Sie der Anleitung für die Migration zu AndroidX.
Android TV App – Voraussetzungen
Damit Cast Connect in deiner Android TV App unterstützt wird, musst du Ereignisse aus einer Mediensitzung erstellen und unterstützen. Die von Ihrer Mediensitzung bereitgestellten Daten enthalten die grundlegenden Informationen zu Ihrem Medienstatus, z. B. Position und Wiedergabestatus. Die Mediensitzung wird auch von der Cast Connect-Bibliothek verwendet, um zu signalisieren, wenn bestimmte Nachrichten von einem Absender empfangen wurden, z. B. zum Pausieren.
Weitere Informationen zu Mediensitzungen und zum Initialisieren einer Mediensitzung finden Sie im Leitfaden zur Arbeit mit Mediensitzungen.
Lebenszyklus von Mediensitzungen
Ihre App sollte zu Beginn der Wiedergabe eine Mediensitzung erstellen und sie loslassen, wenn sie nicht mehr gesteuert werden kann. Wenn es sich bei Ihrer App beispielsweise um eine Video-App handelt, sollten Sie die Sitzung freigeben, wenn der Nutzer die Wiedergabeaktivität beendet – entweder, indem Sie „Zurück“ auswählen, um andere Inhalte zu durchsuchen, oder indem Sie die App im Hintergrund ausführen. Wenn es sich bei Ihrer App um eine Musik-App handelt, sollten Sie sie veröffentlichen, wenn Ihre App keine Medien mehr abspielt.
Sitzungsstatus wird aktualisiert
Die Daten in Ihrer Mediensitzung sollten bezüglich des Status Ihres Players auf dem neuesten Stand gehalten werden. Wenn die Wiedergabe beispielsweise pausiert ist, solltest du den Wiedergabestatus sowie die unterstützten Aktionen aktualisieren. In den folgenden Tabellen sind die Bundesstaaten aufgeführt, für deren Aktualisierung Sie verantwortlich sind.
MediaMetadataCompat
Metadatenfeld | Beschreibung |
---|---|
METADATA_KEY_TITLE (erforderlich) | Der Medientitel. |
METADATA_KEY_DISPLAY_SUBTITLE | Die Unterüberschrift. |
METADATA_KEY_DISPLAY_ICON_URI | Die Symbol-URL. |
METADATA_KEY_DURATION (erforderlich) | Mediendauer. |
METADATA_KEY_MEDIA_URI | Content ID |
METADATA_KEY_ARTIST | Der Künstler. |
METADATA_KEY_ALBUM | Das Album. |
PlaybackStateCompat
Erforderliche Methode | Beschreibung |
---|---|
setActions() | Legt unterstützte Medienbefehle fest. |
setState() | Wiedergabestatus und aktuelle Position festlegen |
MediaSessionCompat
Erforderliche Methode | Beschreibung |
---|---|
setRepeatMode() | Legt den Wiederholungsmodus fest. |
setShuffleMode() | Legt den Zufallsmix fest. |
setMetadata() | Legt Medienmetadaten fest. |
setPlaybackState() | Legt den Wiedergabestatus fest. |
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); }
Transportsteuerung handhaben
In Ihrer App sollte ein Callback für die Steuerung der Mediensitzungstransport implementiert sein. Die folgende Tabelle zeigt, welche Transportsteuerungsaktionen sie verarbeiten müssen:
MediaSessionCompat.Callback
Aktionen | Beschreibung |
---|---|
onPlay() | Fortsetzen |
onPause() | Pausieren |
onSeekTo() | Zu einer Position springen |
onStop() | Aktuelle Medien anhalten |
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-Unterstützung konfigurieren
Wenn eine Senderanwendung eine Startanfrage sendet, wird ein Intent mit einem Anwendungs-Namespace erstellt. Ihre Anwendung ist für die Verarbeitung und das Erstellen einer Instanz des CastReceiverContext
-Objekts beim Start der TV-App verantwortlich. Das CastReceiverContext
-Objekt wird benötigt, um mit Cast zu interagieren, während die TV-App ausgeführt wird. Mit diesem Objekt kann Ihre TV-App Streaming-Nachrichten von beliebigen verbundenen Absendern empfangen.
Einrichtung von Android TV
Startabsichtsfilter hinzufügen
Fügen Sie der Aktivität, die den Start-Intent der Absender-App verarbeiten soll, einen neuen Intent-Filter hinzu:
<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>
Anbieter von Empfängeroptionen angeben
Sie müssen eine ReceiverOptionsProvider
implementieren, um CastReceiverOptions
bereitzustellen:
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(); } }
Geben Sie dann den Optionsanbieter in AndroidManifest
an:
<meta-data
android:name="com.google.android.gms.cast.tv.RECEIVER_OPTIONS_PROVIDER_CLASS_NAME"
android:value="com.example.mysimpleatvapplication.MyReceiverOptionsProvider" />
ReceiverOptionsProvider
wird verwendet, um den CastReceiverOptions
bereitzustellen, wenn CastReceiverContext
initialisiert wird.
Kontext des Streamingempfängers
Initialisieren Sie CastReceiverContext
beim Erstellen der Anwendung:
override fun onCreate() { CastReceiverContext.initInstance(this) ... }
@Override public void onCreate() { CastReceiverContext.initInstance(this); ... }
Starten Sie CastReceiverContext
, wenn Ihre App in den Vordergrund verschoben wird:
CastReceiverContext.getInstance().start()
CastReceiverContext.getInstance().start();
Rufe stop()
im CastReceiverContext
auf, nachdem die App bei Video-Apps oder Apps, die die Hintergrundwiedergabe nicht unterstützen, in den Hintergrund gewechselt hat:
// Player has stopped. CastReceiverContext.getInstance().stop()
// Player has stopped. CastReceiverContext.getInstance().stop();
Wenn deine App die Hintergrundwiedergabe unterstützt, rufe außerdem stop()
für CastReceiverContext
auf, wenn die Wiedergabe im Hintergrund beendet wird.
Wir empfehlen dringend, den LifecycleObserver aus der Bibliothek androidx.lifecycle
zu verwenden, um Aufrufe von CastReceiverContext.start()
und CastReceiverContext.stop()
zu verwalten, insbesondere wenn Ihre native Anwendung mehrere Aktivitäten hat. Dadurch werden Race-Bedingungen vermieden, wenn Sie start()
und stop()
von verschiedenen Aktivitäten aus aufrufen.
// 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 mit MediaManager verbinden
Wenn Sie ein MediaSession
erstellen, müssen Sie auch das aktuelle MediaSession
-Token an CastReceiverContext
übergeben, damit es weiß, wohin die Befehle gesendet und der Status der Medienwiedergabe abgerufen werden soll:
val mediaManager: MediaManager = receiverContext.getMediaManager() mediaManager.setSessionCompatToken(currentMediaSession.getSessionToken())
MediaManager mediaManager = receiverContext.getMediaManager(); mediaManager.setSessionCompatToken(currentMediaSession.getSessionToken());
Wenn Sie MediaSession
aufgrund einer inaktiven Wiedergabe freigeben, sollten Sie für MediaManager
ein Null-Token festlegen:
myPlayer.stop() mediaSession.release() mediaManager.setSessionCompatToken(null)
myPlayer.stop(); mediaSession.release(); mediaManager.setSessionCompatToken(null);
Wenn Ihre App die Wiedergabe von Medien unterstützt, während sie im Hintergrund ausgeführt wird, sollten Sie CastReceiverContext.stop()
nur dann aufrufen, wenn sie im Hintergrund ausgeführt wird und keine Medien mehr abspielt. Beispiel:
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(); } }
ExoPlayer mit Cast Connect verwenden
Wenn du Exoplayer
verwendest, kannst du mit MediaSessionConnector
die Sitzung und alle zugehörigen Informationen, einschließlich des Wiedergabestatus, automatisch aufrechterhalten, anstatt die Änderungen manuell nachzuverfolgen.
MediaSessionConnector.MediaButtonEventHandler
kann zum Verarbeiten von MediaButton-Ereignissen durch Aufrufen von setMediaButtonEventHandler(MediaButtonEventHandler)
verwendet werden, die normalerweise standardmäßig von MediaSessionCompat.Callback
verarbeitet werden.
Um MediaSessionConnector
in Ihre App einzubinden, fügen Sie Ihrer Player-Aktivitätsklasse oder dem Speicherort, an dem Sie Ihre Mediensitzung verwalten, Folgendes hinzu:
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); ... } }
Einrichtung der Sender-App
Cast Connect-Unterstützung aktivieren
Nachdem du deine Sender-App mit Cast Connect-Unterstützung aktualisiert hast, kannst du die Bereitschaft deklarieren, indem du das Flag androidReceiverCompatible
auf LaunchOptions
auf „true“ setzt.
Erfordert play-services-cast-framework
-Version 19.0.0
oder höher.
Das Flag androidReceiverCompatible
wird in LaunchOptions
gesetzt, das Teil von CastOptions
ist:
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(); } }
Erfordert google-cast-sdk
-Version v4.4.8
oder höher.
Das Flag androidReceiverCompatible
wird in GCKLaunchOptions
gesetzt, das Teil von GCKCastOptions
ist:
let options = GCKCastOptions(discoveryCriteria: GCKDiscoveryCriteria(applicationID: kReceiverAppID)) ... let launchOptions = GCKLaunchOptions() launchOptions.androidReceiverCompatible = true options.launchOptions = launchOptions GCKCastContext.setSharedInstanceWith(options)
Erfordert die Chromium-Browserversion M87
oder höher.
const context = cast.framework.CastContext.getInstance(); const castOptions = new cast.framework.CastOptions(); castOptions.receiverApplicationId = kReceiverAppID; castOptions.androidReceiverCompatible = true; context.setOptions(castOptions);
Einrichtung der Cast Developer Console
Android TV App konfigurieren
Füge in der Cast Developer Console den Paketnamen deiner Android TV-App hinzu, um ihn mit deiner Cast App-ID zu verknüpfen.
Entwicklergeräte registrieren
Registriere die Seriennummer des Android TV-Geräts, das du für die Entwicklung in der Cast Developer Console verwenden möchtest.
Ohne Registrierung funktioniert Cast Connect aus Sicherheitsgründen nur bei Apps, die über den Google Play Store installiert wurden.
Weitere Informationen zur Registrierung eines Cast- oder Android TV-Geräts für die Cast-Entwicklung findest du auf der Registrierungsseite.
Medien werden geladen
Wenn du bereits Deeplink-Unterstützung in deiner Android TV-App implementiert hast, sollte in deinem Android TV-Manifest eine ähnliche Definition konfiguriert sein:
<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>
Nach Entität auf Absender laden
Auf den Absendern können Sie den Deeplink übergeben, indem Sie entity
in den Medieninformationen für die Ladeanfrage festlegen:
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)
Erfordert die Chromium-Browserversion M87
oder höher.
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);
Der Ladebefehl wird über einen Intent mit Ihrem Deeplink und dem Paketnamen gesendet, den Sie in der Developer Console definiert haben.
ATV-Anmeldedaten für Absender festlegen
Es ist möglich, dass die Web Receiver App und die Android TV App unterschiedliche Deeplinks und credentials
unterstützen, z. B. wenn die Authentifizierung auf beiden Plattformen unterschiedlich verarbeitet wird. Du kannst alternative entity
und credentials
für Android TV angeben, um dieses Problem zu beheben:
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)
Erfordert die Chromium-Browserversion M87
oder höher.
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);
Wenn die Web Receiver-Anwendung gestartet wird, verwendet sie in der Ladeanfrage entity
und credentials
. Wenn deine Android TV App jedoch gestartet wird, überschreibt das SDK entity
und credentials
durch atvEntity
und atvCredentials
(falls angegeben).
Laden nach Content ID oder MediaQueueData
Wenn du weder entity
noch atvEntity
, sondern Content ID oder Content-URL in deinen Medieninformationen oder die detaillierteren Media Load-Anfragedaten verwendest, musst du in deiner Android TV-App den folgenden vordefinierten Intent-Filter hinzufügen:
<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>
Auf der Seite des Absenders können Sie ähnlich wie beim Laden nach Entität eine Ladeanfrage mit Ihren Inhaltsinformationen erstellen und load()
aufrufen.
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)
Erfordert die Chromium-Browserversion M87
oder höher.
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);
Lastanfragen verarbeiten
Zum Verarbeiten dieser Ladeanfragen müssen Sie in Ihrer Aktivität die Intents in den Callbacks für den Aktivitätslebenszyklus verarbeiten:
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. ... } }
Wenn MediaManager
erkennt, dass der Intent ein Lade-Intent ist, extrahiert es ein MediaLoadRequestData
-Objekt aus dem Intent und ruft MediaLoadCommandCallback.onLoad()
auf.
Sie müssen diese Methode überschreiben, um die Ladeanfrage zu verarbeiten. Der Callback muss registriert werden, bevor MediaManager.onNewIntent()
aufgerufen wird. Wir empfehlen, den Callback für die Methode „Aktivität“ oder „App“ onCreate()
zu verwenden.
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); }
Zum Verarbeiten des Lade-Intents können Sie den Intent in die von uns definierten Datenstrukturen parsen (MediaLoadRequestData
für Ladeanfragen).
Unterstützte Medienbefehle
Grundlegende Unterstützung für die Wiedergabesteuerung
Grundlegende Integrationsbefehle enthalten die Befehle, die mit Mediensitzungen kompatibel sind. Diese Befehle werden über Callbacks für Mediensitzungen benachrichtigt. Dafür müssen Sie einen Callback für die Mediensitzung registrieren.
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());
Unterstützung für Cast-Steuerungsbefehle
Einige Cast-Befehle sind in MediaSession
nicht verfügbar, z. B. skipAd()
oder setActiveMediaTracks()
.
Außerdem müssen hier einige Warteschlangenbefehle implementiert werden, da die Cast-Warteschlange nicht vollständig mit der MediaSession
-Warteschlange kompatibel ist.
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());
Unterstützte Medienbefehle angeben
Genau wie bei Cast-Receivern sollte in der Android TV App angegeben sein, welche Befehle unterstützt werden, damit Absender bestimmte UI-Steuerelemente aktivieren oder deaktivieren können. Geben Sie Befehle, die Teil von MediaSession
sind, in PlaybackStateCompat
an.
Zusätzliche Befehle sollten in MediaStatusModifier
angegeben werden.
// 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);
Nicht unterstützte Schaltflächen ausblenden
Wenn deine Android TV-App nur eine einfache Mediensteuerung unterstützt, die Web Receiver-App jedoch eine erweiterte Steuerung unterstützt, solltest du darauf achten, dass deine Sender-App beim Streamen an die Android TV-App richtig funktioniert. Wenn deine Android TV-App beispielsweise das Ändern der Wiedergabegeschwindigkeit nicht unterstützt, während deine Web Receiver-App dies unterstützt, solltest du die unterstützten Aktionen auf jeder Plattform richtig festlegen und dafür sorgen, dass deine Absender-App die UI korrekt rendert.
MediaStatus ändern
Zur Unterstützung erweiterter Funktionen wie Titel, Anzeigen, Livestreams und Wiedergabelisten muss deine Android TV-App zusätzliche Informationen bereitstellen, die nicht über MediaSession
ermittelt werden können.
Dazu stellen wir Ihnen die Klasse MediaStatusModifier
zur Verfügung. MediaStatusModifier
wird immer für die MediaSession
ausgeführt, die Sie in CastReceiverContext
festgelegt haben.
So erstellen und übertragen Sie 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();
Unsere Clientbibliothek ruft die Basis-MediaStatus
aus MediaSession
ab. In deiner Android TV-App kann mit einem MediaStatus
-Modifikator zusätzliche Statusangaben angegeben und der Status überschrieben werden.
Einige Status und Metadaten können sowohl in MediaSession
als auch in MediaStatusModifier
festgelegt werden. Wir empfehlen dringend, sie nur in MediaSession
festzulegen. Sie können den Modifikator trotzdem verwenden, um die Status in MediaSession
zu überschreiben. Dies wird nicht empfohlen, da der Status im Modifikator immer eine höhere Priorität hat als die von MediaSession
bereitgestellten Werte.
MediaStatus wird vor dem Senden abgefangen
Wie beim Web Receiver SDK kannst du auch hier einen MediaStatusInterceptor
angeben, um die zu sendende MediaStatus
zu verarbeiten. Wir übergeben eine MediaStatusWriter
, um die MediaStatus
vor dem Senden zu bearbeiten.
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\"}")); } });
Umgang mit Nutzeranmeldedaten
Ihre Android TV-App erlaubt möglicherweise nur bestimmten Nutzern, die App-Sitzung zu starten oder daran teilzunehmen. Erlauben Sie einem Absender beispielsweise nur in folgenden Fällen das Starten oder die Teilnahme:
- Die Absender-App ist im selben Konto und Profil wie die ATV-App angemeldet.
- Die Absender-App ist im selben Konto angemeldet, aber in einem anderen Profil als die ATV-App.
Wenn deine App mehrere oder anonyme Nutzer verarbeiten kann, kannst du weiteren Nutzern erlauben, an der ATV-Sitzung teilzunehmen. Wenn der Nutzer Anmeldedaten angibt, muss Ihre ATV-App diese Anmeldedaten verarbeiten, damit sein Fortschritt und andere Nutzerdaten ordnungsgemäß verfolgt werden können.
Wenn Ihre Absender-App Ihre Android TV-App startet oder beitritt, sollte Ihre Absender-App die Anmeldedaten zur Verfügung stellen, die angeben, wer an der Sitzung teilnimmt.
Bevor ein Absender Ihre Android TV-App startet und ihr beitritt, können Sie mithilfe einer Startprüfung feststellen, ob die Anmeldedaten des Absenders zulässig sind. Falls nicht, startet das Cast Connect SDK deinen Web Receiver.
Anmeldedaten für den Start der Sender-App
Auf der Absenderseite können Sie die CredentialsData
angeben, um anzugeben, wer an der Sitzung teilnimmt.
credentials
ist ein String, der vom Nutzer definiert werden kann, sofern die ATV-App ihn lesen kann. Die credentialsType
definiert, von welcher Plattform die CredentialsData
stammt, oder kann ein benutzerdefinierter Wert sein. Standardmäßig ist sie auf die Plattform eingestellt, von der sie gesendet wird.
Die CredentialsData
wird nur während des Starts oder der Teilnahme an deiner Android TV App übergeben. Wenn Sie die Einstellung neu festlegen, während eine Verbindung besteht, wird sie nicht an Ihre Android TV-App weitergegeben. Wenn Ihr Absender das Profil während der Verbindung wechselt, können Sie entweder in der Sitzung bleiben oder SessionManager.endCurrentCastSession(boolean stopCasting)
aufrufen, wenn Sie der Meinung sind, dass das neue Profil nicht mit der Sitzung kompatibel ist.
Die CredentialsData
für jeden Sender können mithilfe von getSenders
auf der CastReceiverContext
abgerufen werden, um die SenderInfo
zu erhalten, getCastLaunchRequest()
, um die CastLaunchRequest
und dann getCredentialsData()
zu erhalten.
Erfordert play-services-cast-framework
-Version 19.0.0
oder höher.
CastContext.getSharedInstance().setLaunchCredentialsData( CredentialsData.Builder() .setCredentials("{\"userId\": \"abc\"}") .build() )
CastContext.getSharedInstance().setLaunchCredentialsData( new CredentialsData.Builder() .setCredentials("{\"userId\": \"abc\"}") .build());
Erfordert google-cast-sdk
-Version v4.8.1
oder höher.
Kann jederzeit aufgerufen werden, nachdem die Optionen festgelegt wurden: GCKCastContext.setSharedInstanceWith(options)
.
GCKCastContext.sharedInstance().setLaunch( GCKCredentialsData(credentials: "{\"userId\": \"abc\"}")
Erfordert die Chromium-Browserversion M87
oder höher.
Kann jederzeit aufgerufen werden, nachdem die Optionen festgelegt wurden: cast.framework.CastContext.getInstance().setOptions(options);
.
let credentialsData = new chrome.cast.CredentialsData("{\"userId\": \"abc\"}"); cast.framework.CastContext.getInstance().setLaunchCredentialsData(credentialsData);
ATV-Startanfrageprüfung implementieren
Die CredentialsData
wird an deine Android TV App übergeben, wenn ein Absender versucht, den Dienst zu starten oder ihm beizutreten. Sie können eine LaunchRequestChecker
implementieren.
um diese Anfrage zuzulassen oder abzulehnen.
Wenn eine Anfrage abgelehnt wird, wird der Web Receiver geladen, anstatt nativ in der ATV App zu starten. Sie sollten eine Anfrage ablehnen, wenn Ihr ATV nicht in der Lage ist, den Nutzer zu verarbeiten, der den Start oder die Teilnahme beantragt. Dies kann beispielsweise der Fall sein, wenn ein anderer Nutzer in der ATV-App angemeldet ist als fordert und deine App nicht in der Lage ist, die Anmeldedaten zu wechseln, oder wenn derzeit kein Nutzer in der ATV-App angemeldet ist.
Wenn eine Anfrage zulässig ist, wird die ATV-App gestartet. Du kannst dieses Verhalten anpassen, je nachdem, ob deine App das Senden von Ladeanfragen unterstützt, wenn ein Nutzer nicht in der ATV-App angemeldet ist, oder ob eine Nutzerabweichung vorliegt. Dieses Verhalten kann in LaunchRequestChecker
vollständig angepasst werden.
Erstellen Sie eine Klasse, die die CastReceiverOptions.LaunchRequestChecker
-Schnittstelle implementiert:
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; }
Legen Sie ihn dann in der ReceiverOptionsProvider
fest:
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(); } }
Wenn du true
in LaunchRequestChecker
behebst, wird die ATV App gestartet und über false
deine Web Receiver App gestartet.
Benutzerdefinierte Nachrichten senden und empfangen
Mit dem Cast-Protokoll können Sie benutzerdefinierte Stringnachrichten zwischen Absendern und Ihrer Empfängeranwendung senden. Sie müssen einen Namespace (Kanal) registrieren, um Nachrichten zu senden, bevor Sie CastReceiverContext
initialisieren.
Android TV – Benutzerdefinierten Namespace angeben
Sie müssen die unterstützten Namespaces in CastReceiverOptions
während der Einrichtung angeben:
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: Nachrichten senden
// 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: Benutzerdefinierte Namespace-Nachrichten empfangen
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());