This guide shows you how to create a cast-enabled Android Sender App by walking you through an example app.
To follow along with this guide, download our AdvancedExample.zip, which is a complete Android app that implements casting functionality for videos with ads using the IMA Android SDK.
Prerequisites
This guide builds upon existing knowledge of the Google Cast SDK and the IMA SDK for Android. Users should familiarize themselves with:
This guide discusses the specific setup used in the Advanced Example to communicate with the receiver. It doesn't cover standard Cast setup code or normal IMA ad playback. For more details on implementing the receiver, see our HTML5 Receiver Guide.
Media player
The functionality for loading the media on to the receiver is implemented in CastApplication.java, along with the majority of the Cast-specific code. The media that this attempts to load is the content URL, it then also sends a custom messsage to the receiver telling it to request ads using the same ad tag and seek to the sender's current timestamp.
CastApplication.java
private void createMediaPlayer() {
Log.d(TAG, "making media player");
// Create a Remote Media Player
mRemoteMediaPlayer = new RemoteMediaPlayer();
mRemoteMediaPlayer.setOnStatusUpdatedListener(
new RemoteMediaPlayer.OnStatusUpdatedListener() {
@Override
public void onStatusUpdated() {
MediaStatus mediaStatus = mRemoteMediaPlayer.getMediaStatus();
Log.d(TAG, "Media status: " + mediaStatus);
}
});
mRemoteMediaPlayer.setOnMetadataUpdatedListener(
new RemoteMediaPlayer.OnMetadataUpdatedListener() {
@Override
public void onMetadataUpdated() {
MediaInfo mediaInfo = mRemoteMediaPlayer.getMediaInfo();
Log.d(TAG, "Media info: " + mediaInfo);
}
});
try {
Cast.CastApi.setMessageReceivedCallbacks(sApiClient,
mRemoteMediaPlayer.getNamespace(), mRemoteMediaPlayer);
} catch (IOException e) {
Log.e(TAG, "Exception while creating media channel", e);
}
mRemoteMediaPlayer
.requestStatus(sApiClient)
.setResultCallback(
new ResultCallback<RemoteMediaPlayer.MediaChannelResult>() {
@Override
public void onResult(RemoteMediaPlayer.MediaChannelResult result) {
if (!result.getStatus().isSuccess()) {
Log.e(TAG, "Failed to request status.");
} else {
MediaMetadata mediaMetadata =
new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE);
mediaMetadata.putString(MediaMetadata.KEY_TITLE, "My video");
MediaInfo mediaInfo = new MediaInfo.Builder(mContentUrl)
.setContentType("video/mp4")
.setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
.setMetadata(mediaMetadata)
.build();
loadMedia(mediaInfo, false);
}
}
});
}
private void loadMedia(MediaInfo mediaInfo, Boolean autoplay) {
try {
Log.d(TAG, "loading media");
mRemoteMediaPlayer.load(sApiClient, mediaInfo, autoplay)
.setResultCallback(new ResultCallback<RemoteMediaPlayer.MediaChannelResult>() {
@Override
public void onResult(RemoteMediaPlayer.MediaChannelResult result) {
if (result.getStatus().isSuccess()) {
boolean adStarted = mVideoPlayerController.hasAdStarted();
if (mVideoFragment.isVmap() || !adStarted) {
sendMessage("requestAd," + mAdTagUrl + ","
+ mVideoPlayerController.getCurrentContentTime());
} else {
sendMessage("seek,"
+ mVideoPlayerController.getCurrentContentTime());
}
} else {
Log.e(TAG, "Error loading Media : "
+ result.getStatus().getStatusCode());
}
}
});
} catch (Exception e) {
Log.e(TAG, "Problem opening media during loading", e);
}
}
private void sendMessage(String message) {
if (sApiClient != null && incomingMsgHandler != null) {
try {
Log.d(TAG, "Sending message: " + message);
Cast.CastApi.sendMessage(sApiClient, NAMESPACE, message)
.setResultCallback(new ResultCallback<Status>() {
@Override
public void onResult(Status result) {
if (!result.isSuccess()) {
Log.e(TAG, "Sending message failed");
}
}
});
} catch (Exception e) {
Log.e(TAG, "Exception while sending message", e);
}
}
}
Two messages are defined in the onResult
callback: seek
, to resume playback on the
receiver without ads, and requestAd
, which re-requests the same ad tag on the receiver. The
requestAd
message is used for ad playlists and VMAP in this example, but you can define your own
behavior for what should happen when you cast. These messages are comma-delimited, where the first
parameter is the message name, and the remaining parameters are ad-tag and seek time.
MediaRouter callback
The MediaRouter.Callback
handles starting and stopping casting and controls switching
video playback between the Android and cast devices. This involves pausing and seeking the video.
CastApplication.java
private final MediaRouter.Callback mediaRouterCallback = new MediaRouter.Callback() {
@Override
public void onRouteSelected(MediaRouter router, MediaRouter.RouteInfo info) {
// User starts casting. Pause video on device and set Google Cast device.
mVideoPlayerController = mVideoFragment.getVideoPlayerController();
mVideoPlayerController.pause();
mAdTagUrl = mVideoPlayerController.getAdTagUrl();
mContentUrl = mVideoPlayerController.getContentVideoUrl();
CastDevice device = CastDevice.getFromBundle(info.getExtras());
setSelectedDevice(device);
}
@Override
public void onRouteUnselected(MediaRouter router, MediaRouter.RouteInfo info) {
// User stops casting. Resume video on device and seek to current time of Google Cast.
mVideoPlayerController.resume();
if (mCastAdPlaying) {
mVideoPlayerController.seek(mCastContentTime);
} else {
double videoPosition = mRemoteMediaPlayer.getApproximateStreamPosition();
mVideoPlayerController.seek(videoPosition / 1000.0);
}
stopCastApplication();
setSelectedDevice(null);
}
};
Messages
The app defines the MessageReceivedCallback
to allow the receiver to provide notices of time
updates from a casting video and when a casted ad stops and content starts. These are sent by the
receiver and handled by two messages, onContentPauseRequested
and onContentResumeRequested
.
These messages are comma-delimited and formatted similarly to the seek
and requestAd
messages
described above.
CastApplication.java
public final Cast.MessageReceivedCallback incomingMsgHandler =
new Cast.MessageReceivedCallback() {
@Override
public void onMessageReceived(CastDevice castDevice, String namespace,
String message) {
Log.d(TAG, "Receiving message: " + message);
String[] splitMessage = message.split(",");
String event = splitMessage[0];
switch (event) {
case "onContentPauseRequested":
mCastAdPlaying = true;
mCastContentTime = Double.parseDouble(splitMessage[1]);
return;
case "onContentResumeRequested":
mCastAdPlaying = false;
return;
}
}
};
Next steps
At this point you have a good understanding of how the Android sender app interacts with the receiver. To try it out, create a receiver app and register your receiver to receive an app ID that's used with API calls from the sender app.