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

David González

October 31, 2014
Tweet

More Decks by David González

Other Decks in Technology

Transcript

  1. 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(); }
  2. 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)
  3. 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>
  4. 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); … }
  5. 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 {
  6. 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
  7. 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(); }
  8. 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(); }
  9. 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()); } }
  10. 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); } } }
  11. 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); } }
  12. 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); } } }
  13. 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); }
  14. 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); } },
  15. 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; }
  16. 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>
  17. 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); }
  18. 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); }
  19. 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(); }
  20. 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"
  21. 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.