Android Sender App to cast videos to a Receiver App

Posted By : Sapna Sharma | 29-Jun-2015

Android Sender App Receiver App

A sender application or Google Cast application refers to an app running on a mobile device (the sender device).

 

Setup

  • Download the latest version of the Android SDK using the Android SDK Manager.

  • Install the Android Support Libraries through the Android SDK Manager. The support libraries need to be revision 19.0.1 or later.

  • Install the Google Play services SDK through the Android SDK Manager. The Google Play services SDK needs to be revision 4.2 or later.

 

Add Libraries

The following libraries are required :

  • android-support-v7-appcompat : path to this library is <SDK install location>/extras/android/support/v7/appcompat

  • android-support-v7-mediarouter : path to this library is <SDK install location>/extras/android/support/v7/mediarouter

  • google-play-services_lib : path to this library is <SDK install location>/extras/google/google_play_services/libproject/google-play-services_lib

 

Note

  • Import these libraries as library projects for your IDE instead of including their JAR files
  • Android-support-v7-mediarouter has a dependency on android-support-v7-appcompat, so add it by selecting the android-support-v7-mediarouter project Properties, then select Android, and in the Libraries list, add android-support-v7-appcompat.
  • Ensure the build target for each of the imported libraries is same : select the library project Properties and then select Android. Select a different Project Build Target, select "Apply", then re-select the desired target (> API 17) and hit "Apply" again.

 

Development

Android manifest

Add following configuration in AndroidManifest.xml file to use the Cast SDK:

 

Internet permission


 

 

uses-sdk

The minimum Android SDK version that the Cast SDK supports is 9 (GingerBread).

 
 

 

meta-data


 

 

android:theme

The applicationís theme needs to be correctly set based on the minimum Android SDK version.


       ...
    
 

 

Adding the Cast Button

The MediaRouter framework provides a Cast button and a list selection dialog for selecting a route. There are three ways to support a Cast button:

  • Using the MediaRouter ActionBar provider: android.support.v7.app.MediaRouteActionProvider

  • Using the MediaRouter Cast button: android.support.v7.app.MediaRouteButton

  • Developing a custom UI with the MediaRouter APIís and MediaRouter.Callback

This blog describes the use of the MediaRouteActionProvider to add the Cast button to the ActionBar.This action bar button handles all of the state logic needed for hiding or displaying the casting button, as well as coloring it when the app has connected to a casting device.Add the media routing button to our menu.xml as follows :

 

  

 

Add some values to strings.xml, such as the video URL, receiver application ID and general UI strings.You'll need to replace app_id

with your application ID, and video_url with a video that you would want to play.

 

Chromecast App
xxxxx
xxxxx
Play Video
Pause Video
Resume Video
Your Video Title Here
video/mp4
 

 

The UI for this activity consists of a single button that will start a video if the app is connected to a casting device, or can pause/resume a video once it has been started, and the media router button in the action bar.The action bar button will display only if you are connected with chromecast device on the same wifi network.

 

Add following code to your casting activity i.e, MainActivity.java

 

package com.example.chromecastapp;

import android.os.Bundle;
import android.support.v4.view.MenuItemCompat;

import android.support.v7.app.ActionBarActivity;
import android.support.v7.app.MediaRouteActionProvider;
import android.support.v7.media.MediaRouteSelector;
import android.support.v7.media.MediaRouter;
import android.support.v7.media.MediaRouter.RouteInfo;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

import com.google.android.gms.cast.ApplicationMetadata;
import com.google.android.gms.cast.Cast;
import com.google.android.gms.cast.CastDevice;
import com.google.android.gms.cast.CastMediaControlIntent;
import com.google.android.gms.cast.MediaInfo;
import com.google.android.gms.cast.MediaMetadata;
import com.google.android.gms.cast.MediaStatus;
import com.google.android.gms.cast.RemoteMediaPlayer;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.common.api.Status;

import java.io.IOException;

public class MainActivity extends ActionBarActivity {

    private Button mButton;

    private MediaRouter mMediaRouter;
    private MediaRouteSelector mMediaRouteSelector;
    private MediaRouter.Callback mMediaRouterCallback;
    private CastDevice mSelectedDevice;
    private GoogleApiClient mApiClient;
    private RemoteMediaPlayer mRemoteMediaPlayer;
    private Cast.Listener mCastClientListener;
    private boolean mWaitingForReconnect = false;
    private boolean mApplicationStarted = false;
    private boolean mVideoIsLoaded;
    private boolean mIsPlaying;

    @Override
    protected void onCreate( Bundle savedInstanceState ) {
        super.onCreate( savedInstanceState );
        setContentView( R.layout.activity_main );

        mButton = (Button) findViewById( R.id.button );
        mButton.setOnClickListener( new OnClickListener() {
            @Override
            public void onClick( View v ) {
               if( !mVideoIsLoaded )
                   startVideo();
               else
                   controlVideo();
            }
        });

        initMediaRouter();
    }

    private void initMediaRouter() {
        // Configure Cast device discovery
        mMediaRouter = MediaRouter.getInstance( getApplicationContext() );
        mMediaRouteSelector = new MediaRouteSelector.Builder()
                .addControlCategory( CastMediaControlIntent.categoryForCast( getString( R.string.app_id ) ) )
                .build();
        mMediaRouterCallback = new MediaRouterCallback();
    }

    private void initCastClientListener() {
        mCastClientListener = new Cast.Listener() {
            @Override
            public void onApplicationStatusChanged() {
            }

            @Override
            public void onVolumeChanged() {
            }

            @Override
            public void onApplicationDisconnected( int statusCode ) {
                teardown();
            }
        };
    }

    private void initRemoteMediaPlayer() {
        mRemoteMediaPlayer = new RemoteMediaPlayer();
        mRemoteMediaPlayer.setOnStatusUpdatedListener( new RemoteMediaPlayer.OnStatusUpdatedListener() {
            @Override
            public void onStatusUpdated() {
                MediaStatus mediaStatus = mRemoteMediaPlayer.getMediaStatus();
                mIsPlaying = mediaStatus.getPlayerState() == MediaStatus.PLAYER_STATE_PLAYING;
            }
        });

        mRemoteMediaPlayer.setOnMetadataUpdatedListener( new RemoteMediaPlayer.OnMetadataUpdatedListener() {
            @Override
            public void onMetadataUpdated() {
            }
        });
    }

    private void controlVideo() {
        if( mRemoteMediaPlayer == null || !mVideoIsLoaded )
            return;

        if( mIsPlaying ) {
            mRemoteMediaPlayer.pause( mApiClient );
            mButton.setText( getString( R.string.resume_video ) );
        } else {
            mRemoteMediaPlayer.play( mApiClient );
            mButton.setText( getString( R.string.pause_video ) );
        }
    }

    private void startVideo() {
        MediaMetadata mediaMetadata = new MediaMetadata( MediaMetadata.MEDIA_TYPE_MOVIE );
        mediaMetadata.putString( MediaMetadata.KEY_TITLE, getString( R.string.video_title ) );

        MediaInfo mediaInfo = new MediaInfo.Builder( getString( R.string.video_url ) )
                .setContentType( getString( R.string.content_type_mp4 ) )
                .setStreamType( MediaInfo.STREAM_TYPE_BUFFERED )
                .setMetadata( mediaMetadata )
                .build();
        try {
            mRemoteMediaPlayer.load( mApiClient, mediaInfo, true )
                    .setResultCallback( new ResultCallback() {
                        @Override
                        public void onResult( RemoteMediaPlayer.MediaChannelResult mediaChannelResult ) {
                            if( mediaChannelResult.getStatus().isSuccess() ) {
                                mVideoIsLoaded = true;
                                mButton.setText( getString( R.string.pause_video ) );
                            }
                        }
                    } );
        } catch( Exception e ) {
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        // Start media router discovery
        mMediaRouter.addCallback( mMediaRouteSelector, mMediaRouterCallback, MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN );
    }

    @Override
    protected void onPause() {
        if ( isFinishing() ) {
            // End media router discovery
            mMediaRouter.removeCallback( mMediaRouterCallback );
        }
        super.onPause();
    }

    private class MediaRouterCallback extends MediaRouter.Callback {

        @Override
        public void onRouteSelected(MediaRouter router, RouteInfo info) {
            initCastClientListener();
            initRemoteMediaPlayer();

            mSelectedDevice = CastDevice.getFromBundle( info.getExtras() );

            launchReceiver();
        }

        @Override
        public void onRouteUnselected( MediaRouter router, RouteInfo info ) {
            teardown();
            mSelectedDevice = null;
            mButton.setText( getString( R.string.play_video ) );
            mVideoIsLoaded = false;
        }
    }

    private void launchReceiver() {
        Cast.CastOptions.Builder apiOptionsBuilder = Cast.CastOptions
                .builder( mSelectedDevice, mCastClientListener );

        ConnectionCallbacks mConnectionCallbacks = new ConnectionCallbacks();
        ConnectionFailedListener mConnectionFailedListener = new ConnectionFailedListener();
        mApiClient = new GoogleApiClient.Builder( this )
                .addApi( Cast.API, apiOptionsBuilder.build() )
                .addConnectionCallbacks( mConnectionCallbacks )
                .addOnConnectionFailedListener( mConnectionFailedListener )
                .build();

        mApiClient.connect();
    }

    private class ConnectionCallbacks implements GoogleApiClient.ConnectionCallbacks {

        @Override
        public void onConnected( Bundle hint ) {
            if( mWaitingForReconnect ) {
                mWaitingForReconnect = false;
                reconnectChannels( hint );
            } else {
                try {
                    Cast.CastApi.launchApplication( mApiClient, getString( R.string.app_id ), false )
                            .setResultCallback( new ResultCallback() {
                                @Override
                                public void onResult(Cast.ApplicationConnectionResult applicationConnectionResult) {
                                    Status status = applicationConnectionResult.getStatus();
                                    if( status.isSuccess() ) {
                                        //Values that can be useful for storing/logic
                                        ApplicationMetadata applicationMetadata = applicationConnectionResult.getApplicationMetadata();
                                        String sessionId = applicationConnectionResult.getSessionId();
                                        String applicationStatus = applicationConnectionResult.getApplicationStatus();
                                        boolean wasLaunched = applicationConnectionResult.getWasLaunched();

                                        mApplicationStarted = true;
                                        reconnectChannels( null );
                                    }
                                }
                            }
                    );
                } catch ( Exception e ) {

                }
            }
        }

        @Override
        public void onConnectionSuspended(int i) {
            mWaitingForReconnect = true;
        }
    }

    private void reconnectChannels( Bundle hint ) {
        if( ( hint != null ) && hint.getBoolean( Cast.EXTRA_APP_NO_LONGER_RUNNING ) ) {
            //Log.e( TAG, "App is no longer running" );
            teardown();
        } else {
            try {
                Cast.CastApi.setMessageReceivedCallbacks( mApiClient, mRemoteMediaPlayer.getNamespace(), mRemoteMediaPlayer );
            } catch( IOException e ) {
                //Log.e( TAG, "Exception while creating media channel ", e );
            } catch( NullPointerException e ) {
                //Log.e( TAG, "Something wasn't reinitialized for reconnectChannels" );
            }
        }
    }

    private class ConnectionFailedListener implements GoogleApiClient.OnConnectionFailedListener {
        @Override
        public void onConnectionFailed( ConnectionResult connectionResult ) {
            teardown();
        }
    }

    @Override
    public boolean onCreateOptionsMenu( Menu menu ) {
        super.onCreateOptionsMenu( menu );
        getMenuInflater().inflate( R.menu.main, menu );
        MenuItem mediaRouteMenuItem = menu.findItem( R.id.media_route_menu_item );
        MediaRouteActionProvider mediaRouteActionProvider = (MediaRouteActionProvider) MenuItemCompat.getActionProvider( mediaRouteMenuItem );
        mediaRouteActionProvider.setRouteSelector( mMediaRouteSelector );
        return true;
    }

    private void teardown() {
        if( mApiClient != null ) {
            if( mApplicationStarted ) {
                try {
                    Cast.CastApi.stopApplication( mApiClient );
                    if( mRemoteMediaPlayer != null ) {
                        Cast.CastApi.removeMessageReceivedCallbacks( mApiClient, mRemoteMediaPlayer.getNamespace() );
                        mRemoteMediaPlayer = null;
                    }
                } catch( IOException e ) {
                    //Log.e( TAG, "Exception while removing application " + e );
                }
                mApplicationStarted = false;
            }
            if( mApiClient.isConnected() )
                mApiClient.disconnect();
            mApiClient = null;
        }
        mSelectedDevice = null;
        mVideoIsLoaded = false;
    }


    @Override
    public void onDestroy() {
        teardown();
        super.onDestroy();
    }

}

Thanks

About Author

Author Image
Sapna Sharma

Sapna is a bright Android Apps developer using Titanium framework. Sapna likes music and helping needy people.

Request for Proposal

Name is required

Comment is required

Sending message..