Slide 1

Slide 1 text

image Size does not matter 2.83 inches is enough

Slide 2

Slide 2 text

David González dggonzalez +davidgonzalezmalmstein malmstein Android Software Craftsman Google Developer Expert Android

Slide 3

Slide 3 text

image Let’s play a little game

Slide 4

Slide 4 text

image https://www.youtube.com/watch?v=o0Ji2PYPyFU&feature=youtu.be

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

Discovery

Slide 7

Slide 7 text

Media Router mMediaRouter = MediaRouter.getInstance(getApplicationContext()); @Override protected void onStart() { super.onStart(); mMediaRouter.addCallback(mMediaRouteSelector, mMediaRouterCallback, MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN); } @Override protected void onStop() { disconnectApiClient(); mMediaRouter.removeCallback(mMediaRouterCallback); super.onStop(); }

Slide 8

Slide 8 text

Route selector

Slide 9

Slide 9 text

Route selector mMediaRouteSelector = new MediaRouteSelector.Builder() .addControlCategory(CastMediaControlIntent.categoryForCast(APP_ID)) .build(); // These are the framework-supported intents .addControlCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO) .addControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO) .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)

Slide 10

Slide 10 text

Cast button: ActionBar

Slide 11

Slide 11 text

Add the selector to the ActionProvider @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); … // Attach the MediaRouteSelector to the menu item MenuItem mediaRouteMenuItem = menu.findItem(R.id.media_route_menu_item); MediaRouteActionProvider mediaRouteActionProvider= (MediaRouteActionProvider) MenuItemCompat.getActionProvider(mediaRouteMenuItem); mediaRouteActionProvider.setRouteSelector(mSelector); … }

Slide 12

Slide 12 text

Cast button: MediaRouteButton * To use the media route button, the activity must be a subclass of * {@link FragmentActivity} from the android.support.v4 * support library. Refer to support library documentation for details. * * * @see MediaRouteActionProvider * @see #setRouteSelector */ public class MediaRouteButton extends View {

Slide 13

Slide 13 text

private final class MediaRouterCallback extends MediaRouter.Callback { public void onRouteAdded(MediaRouter router, MediaRouter.RouteInfo info public void onRouteRemoved(MediaRouter router, MediaRouter.RouteInfo info public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo info) public void onRouteSelected(MediaRouter router, MediaRouter.RouteInfo info) public void onRouteUnselected(MediaRouter router, MediaRouter.RouteInfo info) public void onProviderAdded(MediaRouter router, MediaRouter.ProviderInfo provider) public void onProviderRemoved(MediaRouter router, MediaRouter.ProviderInfo provider) public void onProviderChanged(MediaRouter router, MediaRouter.ProviderInfo provider) The callback

Slide 14

Slide 14 text

The callback, yet another one private class MediaRouterCallback extends MediaRouter.Callback { @Override public void onRouteSelected(MediaRouter router, RouteInfo route) { CastDevice device = CastDevice.getFromBundle(route.getExtras()); connectApiClient(); } @Override public void onRouteUnselected(MediaRouter router, RouteInfo route) { disconnectApiClient(); leaveGame(); resetUI(); }

Slide 15

Slide 15 text

Connection private void connectApiClient() { Cast.CastOptions apiOptions = Cast.CastOptions.builder(mSelectedDevice, mCastListener) .build(); mApiClient = new GoogleApiClient.Builder(this) .addApi(Cast.API, apiOptions) .addConnectionCallbacks(mConnectionCallbacks) .addOnConnectionFailedListener(mConnectionFailedListener) .build(); mApiClient.connect(); }

Slide 16

Slide 16 text

Connection private class ConnectionCallbacks implements GoogleApiClient.ConnectionCallbacks { @Override public void onConnectionSuspended(int cause) { Log.d(TAG, "ConnectionCallbacks.onConnectionSuspended"); } @Override public void onConnected(Bundle connectionHint) { Log.d(TAG, "ConnectionCallbacks.onConnected"); Cast.CastApi.launchApplication(mApiClient, APP_ID).setResultCallback( new ConnectionResultCallback()); } }

Slide 17

Slide 17 text

Connection private final class ConnectionResultCallback implements ResultCallback { @Override public void onResult(ApplicationConnectionResult result) { Status status = result.getStatus(); ApplicationMetadata appMetaData = result.getApplicationMetadata(); if (status.isSuccess()) { mJoinGameButton.setEnabled(true); Cast.CastApi.setMessageReceivedCallbacks(mApiClient, mGameChannel.getNamespace(), mGameChannel); } else { mJoinGameButton.setEnabled(false); } } }

Slide 18

Slide 18 text

image Messages between Sender and Receiver http://www.wondersandmarvels.com/wp-content/uploads/2014/10/3-pigeons-Pigeoncameras.jpg

Slide 19

Slide 19 text

Tic-Tac-Toe: Movement information public final void move(GoogleApiClient apiClient, int row, int column) { Log.d(TAG, "move: row:" + row + " column:" + column); try { JSONObject payload = new JSONObject(); payload.put(KEY_COMMAND, KEY_MOVE); payload.put(KEY_ROW, row); payload.put(KEY_COLUMN, column); sendMessage(apiClient, payload.toString()); } catch (JSONException e) { Log.e(TAG, "Cannot create object to send a move", e); } }

Slide 20

Slide 20 text

Tic-Tac-Toe: Sending information private final void sendMessage(GoogleApiClient apiClient, String message){ Log.d(TAG, "Sending message: (ns=" + GAME_NAMESPACE + ") " + message); Cast.CastApi.sendMessage(apiClient, GAME_NAMESPACE, message) .setResultCallback( new SendMessageResultCallback(message)); } private final class SendMessageResultCallback implements ResultCallback { .... @Override public void onResult(Status result) { if (!result.isSuccess()) { Log.d(TAG, "Failed to send message. statusCode: " + result.getStatusCode() + " message: " + mMessage); } } }

Slide 21

Slide 21 text

Tic-Tac-Toe: Receiver load function onLoad() { var canvas = document.getElementById("board"); var context = canvas.getContext("2d"); var mBoard = new board(context); var favIcon = document.getElementById("favIcon"); mBoard.clear(); mBoard.drawGrid(); window.gameEngine = new cast.TicTacToe(mBoard); }

Slide 22

Slide 22 text

Tic-Tac-Toe: Receiver message onMessage: function(event) { ... if (message.command == 'join') { this.onJoin(senderId, message); } else if (message.command == 'leave') { this.onLeave(senderId); } else if (message.command == 'move') { this.onMove(senderId, message); } else if (message.command == 'board_layout_request') { this.onBoardLayoutRequest(senderId); } else { console.log('Invalid message command: ' + message.command); } },

Slide 23

Slide 23 text

image

Slide 24

Slide 24 text

image http://fc02.deviantart.net/fs18/i/2012/235/6/1/remote_control_cat_by_rengokuy-dx54f7.jpg

Slide 25

Slide 25 text

No content

Slide 26

Slide 26 text

image https://lh3.googleusercontent.com/-xzAEWO6TYiw/VEq5lQXaKzI/AAAAAAAADVY/Ga2AQ9v_EMo/w1184-h888-no/2014%2B-%2B1

Slide 27

Slide 27 text

Default Receiver UI not customizable Hosted by Google CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID

Slide 28

Slide 28 text

Styled Media Receiver .background { background: center no-repeat url(background.png); } .logo { background-image: url(logo.png); } .progressBar { background-color: rgb(238, 255, 65); } .splash { background-image: url(splash.png); } .watermark { background-image: url(watermark.png); background-size: 57px 57px; }

Slide 29

Slide 29 text

Custom receiver

Slide 30

Slide 30 text

Custom receiver Example minimum receiver window.mediaElement = document.getElementById('media'); window.mediaManager = new cast.receiver.MediaManager(window.mediaElement); window.castReceiverManager = cast.receiver.CastReceiverManager.getInstance(); window.castReceiverManager.start();

Slide 31

Slide 31 text

Tip: Host in Google Drive

Slide 32

Slide 32 text

Cast Companion Library https://github.com/googlecast/CastCompanionLibrary-android mCastMgr = VideoCastManager.initialize(context, BuildConfig.CAST_APP_ID, null, null); if (BuildConfig.DEBUG) { mCastMgr.enableFeatures(VideoCastManager.FEATURE_NOTIFICATION | VideoCastManager.FEATURE_LOCKSCREEN | VideoCastManager.FEATURE_DEBUGGING); } else { mCastMgr.enableFeatures(VideoCastManager.FEATURE_NOTIFICATION | VideoCastManager.FEATURE_LOCKSCREEN); }

Slide 33

Slide 33 text

Callbacks, remember? @Override public void onApplicationConnected(ApplicationMetadata appMetadata, String sessionId, boolean wasLaunched) { if (mPlaybackState == PlaybackState.PLAYING) { notifyPauseToListeners(); try { notifyRemoteLoadToListeners(); } catch (Exception e) { notifyExceptionToListeners(e); } } updatePlaybackLocation(PlaybackLocation.REMOTE); }

Slide 34

Slide 34 text

Callbacks, remember? @Override public void onApplicationDisconnected(int errorCode) { updatePlaybackLocation(PlaybackLocation.LOCAL); } @Override public void onDisconnected() { mPlaybackState = PlaybackState.PAUSED; mLocation = PlaybackLocation.LOCAL; } @Override public void onRemoteMediaPlayerMetadataUpdated() { mRemoteMediaInformation = castManager.getRemoteMediaInformation(); }

Slide 35

Slide 35 text

Receiver: MediaInfo public void createMediaInfo(String title, String description, String author, String streamUrl, String mediaType, int streamType) { MediaMetadata metadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE); metadata.putString(MediaMetadata.KEY_SUBTITLE, description); metadata.putString(MediaMetadata.KEY_TITLE, title); metadata.putString(MediaMetadata.KEY_STUDIO, author); metadata.addImage(new WebImage(Uri.parse(imageUrl))); mSelectedMedia = new MediaInfo.Builder(streamUrl) .setStreamType(streamType) .setContentType(mediaType) .setMetadata(metadata) .build(); } MediaInfo.STREAM_TYPE_LIVE MediaInfo.STREAM_TYPE_BUFFERED "application/x-mpegURL" "video/mp4"

Slide 36

Slide 36 text

Tip: Enable CORS XMLHttpRequest cannot load http://artestras.vo.llnwd. net/v2/am/tvguide/HLS/055209-000-A_HQ_1_VOA_01519674_MP4- 2200_AMM-HLS/055209-000-A_HQ_1_VOA_01519674_MP4-2200_AMM-HLS. m3u8. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'https://www.arte.tv' is therefore not allowed access.

Slide 37

Slide 37 text

Cast Activity getCastManager().startCastControllerActivity( this, mSelectedMedia, position, true);

Slide 38

Slide 38 text

Tip: Make Cast button always available

Slide 39

Slide 39 text

Further reading https://developers.google.com/cast/docs/design_checklist http://stackoverflow.com/questions/tagged/google-cast https://github.com/googlecast/CastCompanionLibrary-android https://github.com/malmstein/cast_tictactoe

Slide 40

Slide 40 text

David González dggonzalez +davidgonzalezmalmstein malmstein Android Software Craftsman Google Developer Expert Android Trolling time, questions?