Warning: The IMA SDK for Cast SDK v2 has been officially deprecated, as of November 17th, 2021. All existing users are urged to migrate to CAF native Ad Breaks.

Android sender app

Stay organized with collections Save and categorize content based on your preferences.

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.