Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Size does not matter, 2.83 inches is enough

Size does not matter, 2.83 inches is enough

Talk from Droidcon UK 2014 explaining how to create a small game with Chromecast and best practices when remote video streaming

820df515de752bffa0ce2644a7927186?s=128

David González

October 31, 2014
Tweet

More Decks by David González

Other Decks in Technology

Transcript

  1. image Size does not matter 2.83 inches is enough

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

    Expert Android
  3. image Let’s play a little game

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

  5. None
  6. Discovery

  7. 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(); }
  8. Route selector

  9. 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)
  10. Cast button: ActionBar <?xml version="1.0" encoding="utf-8"?> <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>
  11. 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); … }
  12. Cast button: MediaRouteButton * To use the media route button,

    the activity must be a subclass of * {@link FragmentActivity} from the <code>android.support.v4</code> * support library. Refer to support library documentation for details. * </p> * * @see MediaRouteActionProvider * @see #setRouteSelector */ public class MediaRouteButton extends View {
  13. 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
  14. 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(); }
  15. 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(); }
  16. 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()); } }
  17. Connection private final class ConnectionResultCallback implements ResultCallback<ApplicationConnectionResult> { @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); } } }
  18. image Messages between Sender and Receiver http://www.wondersandmarvels.com/wp-content/uploads/2014/10/3-pigeons-Pigeoncameras.jpg

  19. 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); } }
  20. 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<Status> { .... @Override public void onResult(Status result) { if (!result.isSuccess()) { Log.d(TAG, "Failed to send message. statusCode: " + result.getStatusCode() + " message: " + mMessage); } } }
  21. 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); }
  22. 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); } },
  23. image

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

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

  27. Default Receiver UI not customizable Hosted by Google CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID

  28. 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; }
  29. Custom receiver

  30. Custom receiver <html> <head> <title>Example minimum receiver</title> <script src="//www.gstatic.com/cast/sdk/libs/receiver/2.0.0/cast_receiver.js"></script> </head>

    <body> <video id='media'/> <script> window.mediaElement = document.getElementById('media'); window.mediaManager = new cast.receiver.MediaManager(window.mediaElement); window.castReceiverManager = cast.receiver.CastReceiverManager.getInstance(); window.castReceiverManager.start(); </script> </body> </html>
  31. Tip: Host in Google Drive

  32. 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); }
  33. 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); }
  34. 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(); }
  35. 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"
  36. 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.
  37. Cast Activity getCastManager().startCastControllerActivity( this, mSelectedMedia, position, true);

  38. Tip: Make Cast button always available

  39. 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

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

    Expert Android Trolling time, questions?