Enabling your android app to cast media to other devices such as a google home speaker or a chromecast enabled TV isn’t that scary but it does require a fair amount of setting up.
Steps:
- 1 Configure Android Studio
- Add the Cast SDK to the project
- Create the CastOptionsProvider and ExpandedControls
- Add the Media Route button
- Set up the Session Manager
- Set up the RemoteClient
Step one Configuring your IDE:
Update your android studio and SDK to the latest versions available.
Next install (if you don’t already have it) the Google Play Services SDK
Step two Adding the Cast SDK to your app:
Open your android studio project and add the following libraries to your build.gradle file
implementation 'com.android.support:mediarouter-v7:27.0.2' implementation 'com.google.android.gms:play-services-cast-framework:11.6.2'
Step three Create the CastOptionsProvider and the Expanded controls
Next create a new class that implements the OptionsProvider interface
public class CastOptionsProvider implements OptionsProvider { @Override public CastOptions getCastOptions(Context appContext) { List<String> buttonActions = new ArrayList<>(); buttonActions.add(MediaIntentReceiver.ACTION_REWIND); buttonActions.add(MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK); buttonActions.add(MediaIntentReceiver.ACTION_FORWARD); buttonActions.add(MediaIntentReceiver.ACTION_STOP_CASTING); // Showing "play/pause" and "stop casting" in the compat view of the notification. int[] compatButtonActionsIndicies = new int[]{ 1, 3 }; // Builds a notification with the above actions. Each tap on the "rewind" and // "forward" buttons skips 30 seconds. NotificationOptions notificationOptions = new NotificationOptions.Builder() .setTargetActivityClassName(ExpandedControlsActivity.class.getName()) .setActions(buttonActions, compatButtonActionsIndicies) .setSkipStepMs(30 * DateUtils.SECOND_IN_MILLIS) .build(); CastMediaOptions mediaOptions = new CastMediaOptions.Builder() .setNotificationOptions(notificationOptions) .setExpandedControllerActivityClassName(ExpandedControlsActivity.class.getName()) .build(); return new CastOptions.Builder() .setReceiverApplicationId(CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID) .setCastMediaOptions(mediaOptions) .build(); } @Override public List<SessionProvider> getAdditionalSessionProviders(Context context) { return null; } }
The Cast Options Provider handles the options for the media control widgets such as the expanded controls, the notification controls and the mini controller.
public class ExpandedControlsActivity extends ExpandedControllerActivity{ @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); getMenuInflater().inflate(R.menu.video_menu, menu); CastButtonFactory.setUpMediaRouteButton(this, menu, R.id.media_route_menu_item); return true; } }
The Expanded Controls Activity starts when casting is enabled. It contains the playback and seek controls
Step four Add the Media route button
Next add a Media route button. This is also provided by the cast sdk. If you are using an AppCompat activity it can be added as part of the menu:
<menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <item android:id="@+id/media_route_menu_item" android:title="@string/media_route_menu_title" app:actionProviderClass="android.support.v7.app.MediaRouteActionProvider" app:showAsAction="always" /> </menu>
Step five Manage the Cast Sessions
Next we need a Cast Context and a Session Manager that automatically manages our device sessions and can handle the network operations. In the activity that displays your video, create a Cast Context object and get its Session Manager:
public class VideoActivity extends AppCompatActivity { private CastContext mCastContext; private CastSession mCastSession; private SessionManagerListener<CastSession> mSessionManagerListener; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.video__video); myToolbar = findViewById(R.id.app_toolbar); fallbackView = findViewById(R.id.fallback_video); controller = new MediaController(this); controller.setAnchorView(fallbackView); fallbackView.setMediaController(controller); fallbackView.setVisibility(View.GONE); progressDialog = new ProgressDialog(this); setSupportActionBar(myToolbar); getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayShowHomeEnabled(true); getSupportActionBar().setHomeAsUpIndicator(R.drawable.back_white); getSupportActionBar().setDisplayShowTitleEnabled(false); Paper.init(this); ButterKnife.bind(this); mCastContext = CastContext.getSharedInstance(this); mCastStateListener = new CastStateListener() { @Override public void onCastStateChanged(int newState) { if (newState != CastState.NO_DEVICES_AVAILABLE) { showIntroductoryOverlay(); } } }; mCastSession = mCastContext.getSessionManager().getCurrentCastSession(); mSessionManagerListener = new SessionManagerListener<CastSession>() { @Override public void onSessionEnded(CastSession session, int error) { onApplicationDisconnected(session); } @Override public void onSessionResumed(CastSession session, boolean wasSuspended) { onApplicationConnected(session); } @Override public void onSessionResumeFailed(CastSession session, int error) { onApplicationDisconnected(session); } @Override public void onSessionStarted(CastSession session, String sessionId) { onApplicationConnected(session); } @Override public void onSessionStartFailed(CastSession session, int error) { onApplicationDisconnected(session); } @Override public void onSessionStarting(CastSession session) { } @Override public void onSessionEnding(CastSession session) { } @Override public void onSessionResuming(CastSession session, String sessionId) { } @Override public void onSessionSuspended(CastSession session, int reason) { } }; mCastContext.getSessionManager().addSessionManagerListener( mSessionManagerListener, CastSession.class); } }
Step six Set up the Remote Media Client
Media playback on the cast device is handled by a Remote Media Client object. This contains listeners that track status updates and progress information of the media
remoteMediaClient = mCastSession.getRemoteMediaClient(); if (remoteMediaClient == null) { return; } remoteMediaClient.addProgressListener(new RemoteMediaClient.ProgressListener() { @Override public void onProgressUpdated(long l, long l1) { playerPosition = l; // Log.e("PLAYER_POS_PROG",String.valueOf(playerPosition)); } },1000); remoteMediaClient.addListener(new RemoteMediaClient.Listener() { @Override public void onStatusUpdated() { Intent intent = new Intent(VideoActivity.this, ExpandedControlsActivity.class); startActivity(intent); remoteMediaClient.removeListener(this); } @Override public void onMetadataUpdated() { } @Override public void onQueueStatusUpdated() { } @Override public void onPreloadStatusUpdated() { } @Override public void onSendingRemoteMediaRequest() { } @Override public void onAdBreakStatusUpdated() { } });
The Cast SDK requires some additional metadata for the proper presentation of the cast UI elements. This is handled by the Media Info and Media Metadata objects.
private MediaInfo buildMediaInfo() { MediaMetadata movieMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE); movieMetadata.putString(MediaMetadata.KEY_SUBTITLE, VIDEO_TITLE); movieMetadata.putString(MediaMetadata.KEY_TITLE, VIDEO_SUBTITLE); movieMetadata.addImage(new WebImage(Uri.parse(VIDEO_THUMBNAIL_URL))); return new MediaInfo.Builder(VIDEO_URL) .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED) .setContentType("videos/mp4") .setMetadata(movieMetadata) .build(); }
To start the casting all we now need to do is load the media and it’s metadata into the Remote Client
remoteMediaClient.load(buildMediaInfo(), true,playerPosition);
This starts casting the media to a compatible device at the position set by the Player Position variable. Tracking the play progress to resume where the media was paused between devices is done by the Progress Listener.
Adding cast functionality to your app may seem daunting at first but it is pretty straightforward and can be implemented on top of either the standard android media framework or with a custom video playback solution.