This document assumes you already have a cast-enabled mobile app and a working Tizen TV app. It walks through and compares the steps needed to incorporate Smart View SDK support into a standard Chromecast integration that uses a custom receiver app. This example is based on Android but the same principles can be easily be extended to cover the iOS or Javascript APIs.
If you are using the MediaRouter library method to implement Google Cast, Please switch to custom UI to enable Samsung Smart View within a single cast icon. All the code in this document is available in the Integrated Device Discovery Example in github
The sample code written below for Android.
APIs to start "scanning" the network for TVs and display a list to the user as they are found. Then allow the user to select a device to connect to. "Your mobile device and TV device need to be on the same WIFI network"
Example GoogleCast Android API Usage
mMediaRouter = MediaRouter.getInstance(getApplicationContext()); private class MyMediaRouterCallback extends MediaRouter.Callback { @Override public void onRouteSelected(MediaRouter router, RouteInfo info) { mSelectedDevice = CastDevice.getFromBundle(info.getExtras()); String routeId = info.getId(); ... } @Override public void onRouteUnselected(MediaRouter router, RouteInfo info) { teardown(); mSelectedDevice = null; } } @Override protected void onStart() { super.onStart(); mMediaRouter.addCallback(mMediaRouteSelector, mMediaRouterCallback, MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY); } @Override protected void onStop() { mMediaRouter.removeCallback(mMediaRouterCallback); super.onStop(); }
Example SmartView SDK Android API Usage
// Get an instance of Search Search search = Service.search(getContext()); // Add a listener for the service found event search.setOnServiceFoundListener( new OnServiceFoundListener() { @Override public void onFound(Service service) { // Add service to a displayed list //where your user can select one. } } ); // Add a listener for the service lost event search.setOnServiceLostListener( new OnServiceLostListener() { @Override public void onLost(Service service) { // Remove this service //from the displayed list. } } ); // Start the discovery process search.start(); // Do something while we wait for services to be discovered. ... // Stop the discovery process after some amount of time, search.stop();
Sender app launches the receiver app. And receiver app could be installed TV Apps.
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 connectionHint) { if (mWaitingForReconnect) { Cast.CastApi.launchApplication(mApiClient, "YOUR_APPLICATION_ID", false) .setResultCallback( new ResultCallback() { @Override public void onResult(Cast.ApplicationConnectionResult result) { Status status = result.getStatus(); if (status.isSuccess()) { ApplicationMetadata applicationMetadata = result.getApplicationMetadata(); String sessionId = result.getSessionId(); String applicationStatus = result.getApplicationStatus(); boolean wasLaunched = result.getWasLaunched(); ... } else { teardown(); } } }); }
// Tizen WebApp String url = "YOUR_APPLICATION_ID"; // Example channel id String channelId = "com.samsung.multiscreen.helloworld"; // Get an instance of Application. Application application = service.createApplication(url, channelId); // Listen for the connect event application.setOnConnectListener(new OnConnectListener() { @Override public void onConnect(Client client) { Log.d(LOGTAG, "Application.onConnect():" + client.toString()); } }); // Connect and launch the application. // When you connect to a service, the specified application will // be launched automatically. application.connect(new Result() { @Override public void onSuccess(Client client) { // The application is launched, and is ready to accept messages. } @Override public void onError(Error error) { } );
Sender sends a message to the receiver over the communication channel. and you can send messages to/from any or all clients connected including the TV. furthermore you can receive incoming messages from another device.
/************* 1. Send **************/ private void sendMessage(String message) { if (mApiClient != null && mHelloWorldChannel != null) { try { Cast.CastApi.sendMessage(mApiClient ,mHelloWorldChannel.getNamespace() ,message) .setResultCallback( new ResultCallback() { @Override public void onResult(Status result) { if (!result.isSuccess()) { Log.e(TAG, "Sending message failed"); } } }); } catch (Exception e) { Log.e(TAG, "Exception while sending message", e); } } } /************* 2. Receive **************/ public String getNamespace() { return "urn:x-cast:com.example.custom"; } @Override public void onMessageReceived(CastDevice castDevice, String namespace, String message) { Log.d(TAG, "onMessageReceived: " + message); } } Cast.CastApi.launchApplication(mApiClient,"YOUR_APPLICATION_ID" , false).setResultCallback( new ResultCallback() { @Override public void onResult(Cast.ApplicationConnectionResult result){ Status status = result.getStatus(); if (status.isSuccess()) { mHelloWorldChannel = new HelloWorldChannel(); try { Cast.CastApi.setMessageReceivedCallbacks(mApiClient, mHelloWorldChannel.getNamespace(), mHelloWorldChannel); } catch (IOException e) { Log.e(TAG, "Exception while creating channel", e); } } } });
/************* 1. Send **************/ // Note: The TV application is designated as the "HOST" String event = "say"; String messageData = "Hello World!"; // Send a message to the TV application only, by default. application.publish(event, messageData); // Send a message to the TV application, explicitly. application.publish(event, messageData, Message.TARGET_HOST); // Send a "broadcast" message to all clients EXCEPT yourself. application.publish(event, messageData, Message.TARGET_BROADCAST); // Send a message to all clients INCLUDING yourself application.publish(event, messageData, Message.TARGET_ALL); // Send a message to a specific client String clientId = "123467"; // Assuming that this is a valid id Client client = channel.getClients().get(clientId); application.publish(event, messageData, client); // Send a message to a list of clients List clients = ... application.publish(event, messageData, clients); // Send a binary message to the TV application only, by default. byte[] payload = {0x00, 0x01, 0x02, 0x03}; application.publish(event, messageData, payload); /************* 2. Receive **************/ // The event that this client is interested in // receiving published messages. String event = "say"; // Listen for a message by event application.addOnMessageListener(event, new OnMessageListener() { @Override public void onMessage(Message message) { Log.d(LOGTAG, "message: " + message.toString()); // Got a message with some data or a binary payload. } });
window.onload = function() { window.castReceiverManager = cast.receiver.CastReceiverManager.getInstance(); console.log('Starting Receiver Manager'); // handler for the 'ready' event castReceiverManager.onReady = function(event) { }; // handler for 'senderconnected' event castReceiverManager.onSenderConnected = function(event) { }; // handler for 'senderdisconnected' event castReceiverManager.onSenderDisconnected = function(event) { } }; // create a CastMessageBus to handle messages // for a custom namespace window.messageBus = window.castReceiverManager.getCastMessageBus( 'urn:x-cast:com.google.cast.sample.helloworld'); // handler for the CastMessageBus message event window.messageBus.onMessage = function(event) { // display the message from the sender displayText(event.data); // inform all senders on the CastMessageBus // of the incoming message event // sender message listener will be invoked window.messageBus.send(event.senderId, event.data); } // initialize the CastReceiverManager // with an application status message window.castReceiverManager.start ({statusText: "Application is starting"}); };
window.msf.local(function(err, service){ var channel = service.channel ('com.samsung.multiscreen.helloworld'); channel.connect({name: 'TV'}, function (err) { if(err) return console.error(err); }); channel.on('connect', function(client){ }); channel.on('disconnect', function(client){ }); channel.on('clientConnect', function(client){ }); channel.on('clientDisconnect', function(client){ }); channel.on('say', function(msg, from){ }); });
You have two options for multimedia player in Tizen application.
var mediaElement = document.getElementById('vid'); // Create the media manager. This will handle all media messages by default. window.mediaManager = new cast.receiver.MediaManager(mediaElement); if (event.data['media'] && event.data['media']['contentId']) { console.log('Starting media application'); var url = event.data['media']['contentId']; // Create the Host - much of your interaction with the library uses the Host and // methods you provide to it. window.host = new cast.player.api.Host( {'mediaElement':mediaElement, 'url':url}); var ext = url.substring(url.lastIndexOf('.'), url.length); var initStart = event.data['media']['currentTime'] || 0; var autoplay = event.data['autoplay'] || true; var protocol = null; mediaElement.autoplay = autoplay; // Make sure autoplay get's set if (url.lastIndexOf('.m3u8') >= 0) { // HTTP Live Streaming protocol = cast.player.api.CreateHlsStreamingProtocol(host); } else if (url.lastIndexOf('.mpd') >= 0) { // MPEG-DASH protocol = cast.player.api.CreateDashStreamingProtocol(host); } else if (url.indexOf('.ism/') >= 0) { // Smooth Streaming protocol = cast.player.api.CreateSmoothStreamingProtocol(host); } // How to override a method in Host. I know that it's safe to just provide this // method. host.onError = function(errorCode) { console.log("Fatal Error - " + errorCode); if (window.player) { window.player.unload(); window.player = null; } }; if (protocol !== null) { console.log("Starting Media Player Library"); window.player = new cast.player.api.Player(host); window.player.load(protocol, initStart); } else { window.defaultOnLoad(event); // do the default process } window.player = null; console.log('Application is ready, starting system'); window.castReceiverManager = cast.receiver.CastReceiverManager.getInstance(); castReceiverManager.start();
var config = { url: 'http://media.w3.org/2010/05/bunny/trailer.mp4', player: document.getElementById('av-player'), /*Smooth Streaming examples*/ // 'http://playready.directtaps.net/smoothstreaming/SSWSS720H264/SuperSpeedway_720.ism/Manifest', // url: 'http://playready.directtaps.net/smoothstreaming/TTLSS720VC1/To_The_Limit_720.ism/Manifest' }; if (!url) { url = config.url; } log('videoPlayer open: ' + url); try { webapis.avplay.open(url); webapis.avplay.setDisplayRect( x, y, width, height ); webapis.avplay.setListener(listener); } catch (e) { log(e); } //set bitrates according to the values in your stream manifest // this.setBitrate(477000, 2056000, 2056000, 688000); webapis.avplay.prepare(); webapis.avplay.play(); // pause webapis.avplay.pause(); // stop webapis.avplay.stop(); // close :To destroy the avplay object webapis.avplay.close(); // Backward webapis.avplay.jumpBackward(time); // Forward webapis.avplay.jumpForward(time); // Time var duration = webapis.avplay.getDuration(); var currentTime = webapis.avplay.getCurrentTime();
Example Android API Usage
/**************************************** 1. Discover ****************************************/ //Smart View SDK Search mSearch = Service.search(mContext); mSearch.start(); // Start media router discovery mMediaRouter.addCallback(mMediaRouteSelector, mMediaRouterCallback, MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY); /**************************************** 2. add Listener to discover device *****************************************/ //Smart View SDK mSearch.setOnServiceFoundListener( new Search.OnServiceFoundListener() { @Override public void onFound(Service service) { Log.d(TAG, "Search.onFound() service : " + service.toString()); if(!mDeviceList.contains(service)) { mDeviceList.add(service); mTVListAdapter.add(service); mTVListAdapter.notifyDataSetChanged(); } } } ); mSearch.setOnServiceLostListener( new Search.OnServiceLostListener() { @Override public void onLost(Service service) { Log.d(TAG, "Search.onLost() service : " + service.toString()); mDeviceList.remove(service); mTVListAdapter.remove(service); mTVListAdapter.notifyDataSetChanged(); } } ); /** * For intergrate Google Cast Device discover * Callback for MediaRouter events */ private class MyMediaRouterCallback extends MediaRouter.Callback { @Override public void onRouteAdded(MediaRouter router, MediaRouter.RouteInfo route) { CastDevice castDevice = CastDevice.getFromBundle(route.getExtras()); Log.d(TAG, "onRouteAdded CastDevice = " + route.toString()); if(!mDeviceList.contains(castDevice)) { mDeviceList.add(castDevice); mTVListAdapter.add(castDevice); mTVListAdapter.notifyDataSetChanged(); } } @Override public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo route) { CastDevice castDevice = CastDevice.getFromBundle(route.getExtras()); Log.d(TAG, "onRouteChanged CastDevice = " + route.toString()); if(!mDeviceList.contains(castDevice)) { mDeviceList.add(castDevice); mTVListAdapter.add(castDevice); mTVListAdapter.notifyDataSetChanged(); } } @Override public void onRouteRemoved(MediaRouter router, MediaRouter.RouteInfo route) { Log.d(TAG, "onRouteRemoved"); CastDevice castDevice = CastDevice.getFromBundle(route.getExtras()); mDeviceList.add(castDevice); mTVListAdapter.remove(castDevice); mTVListAdapter.notifyDataSetChanged(); } @Override public void onRouteSelected(MediaRouter router, MediaRouter.RouteInfo info) { Log.d(TAG, "onRouteSelected -- not use"); Log.d(TAG, "onRouteSelected"); } @Override public void onRouteUnselected(MediaRouter router, MediaRouter.RouteInfo info) { Log.d(TAG, "onRouteUnselected ---not use"); } } /**************************************** 3. Discover stop ****************************************/ // Smart View SDK mSearch.stop(); // Google cast. End media router discovery mMediaRouter.removeCallback(mMediaRouterCallback); /**************************************** 4. Launch : How to get MSF "Service" and google cast "CastDevice" ****************************************/ String cName = mTVListAdapter.getItem(which).getClass().getName(); if (Service.class.getName().equals(cName)) { // Launch Smart View SDK app }else if (CastDevice.class.getName().equals(cName)) { // Launch Google Cast app }
Here are two basic applications.
It's very similar and easy to understand how works with each SDK
Google Cast
Smart View SDK