I'm working on an app that controls the Chromecast and whatever's playing on it.
I don't want a sender app, I don't want to register anything to get an api key, none of that stuff.
I'm using so far MediaRouter to control the volume and to see if it's connected to anything.
But I want something like the Google Cast app:
Which knows what's playing and (or at least) the playback state.
Ideally I wouldn't want to use google play services, but if it's the only way, c'est la vie.
I finally figured it out. I had to use the google play services and the google cast sdk v2 but without registering the application.
Libraries included in the project:
compile 'com.android.support:mediarouter-v7:24.0.0'
compile 'com.google.android.gms:play-services-cast-framework:9.2.0'
Beware that in the code below onCreate() and onDestroy() aren't methods in an Activity, Fragment or Service, so don't copy/paste the code and expect it works. The code in those methods must pe copy/pasted in your own methods.
Here are the steps of what's happening:
You select a route either via the cast button either by calling getActiveMediaRoute() which checks for which Chromecast is active (it won't work if nobody is connected to the Chromecast). Override the method or getActiveChromecastRoute() to select based on your preferences
When onRouteSelected() is called a new Cast GoogleApiClient is instanced with options for the selected chromecast
When onApplicationMetadataChanged() is called the code will connect to the current application running on the Chromecast
After the application is successfully connected a new RemoteMediaPlayer is instanced and the MediaStatus is requested
You should get a callback in onStatusUpdated() and after that you can call mRemoteMediaPlayer.getMediaStatus() and it will contain the data about what's being played on the Chromecast.
public static final String CHROMECAST_SIGNATURE = "cast.media.CastMediaRouteProviderService";
private final MediaRouteSelector mSelector;
private final MediaRouter mMediaRouter;
private CastDevice mSelectedDevice;
private Cast.Listener mCastClientListener;
private RemoteMediaPlayer mRemoteMediaPlayer;
#Override
public void onCreate() {
mMediaRouter = MediaRouter.getInstance(context);
mSelector = new MediaRouteSelector.Builder()
// These are the framework-supported intents
.addControlCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO)
.addControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO)
.addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
.build();
mMediaRouter.addCallback(mSelector, mMediaRouterCallback, MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY | MediaRouter.CALLBACK_FLAG_UNFILTERED_EVENTS);
}
#Override
public void onDestroy() {
mMediaRouter.removeCallback(mMediaRouterCallback);
}
#UiThread
private boolean isChromecastActive() {
return getActiveChromecastRoute() != null;
}
#UiThread
private Boolean isChromecastPlaying() {
if (mRemoteMediaPlayer == null || mRemoteMediaPlayer.getMediaStatus() == null) {
return null;
}
// Here you can get the playback status and the metadata for what's playing
// But only after the onStatusUpdated() method is called in the mRemoteMediaPlayer callback
int state = mRemoteMediaPlayer.getMediaStatus().getPlayerState();
return (state == MediaStatus.PLAYER_STATE_BUFFERING || state == MediaStatus.PLAYER_STATE_PLAYING);
}
#UiThread
private MediaRouter.RouteInfo getActiveChromecastRoute() {
for (MediaRouter.RouteInfo route : mMediaRouter.getRoutes()) {
if (isCastDevice(route)) {
if (route.getConnectionState() == MediaRouter.RouteInfo.CONNECTION_STATE_CONNECTED) {
return route;
}
}
}
return null;
}
private int getMediaRouteVolume(#NonNull MediaRouter.RouteInfo route) {
return route.getVolume();
}
private void setMediaRouteVolume(#NonNull MediaRouter.RouteInfo route, int volume) {
route.requestSetVolume(volume);
}
private int getMediaRouteMaxVolume(#NonNull MediaRouter.RouteInfo route) {
return route.getVolumeMax();
}
#UiThread
private MediaRouter.RouteInfo getActiveMediaRoute() {
if (isChromecastActive()) {
MediaRouter.RouteInfo route = getActiveChromecastRoute();
if (route != null) {
if (!route.isSelected()) {
mMediaRouter.selectRoute(route);
}
}
else if (mSelectedDevice != null) {
mSelectedDevice = null;
}
return route;
}
return null;
}
private boolean isCastDevice(MediaRouter.RouteInfo routeInfo) {
return routeInfo.getId().contains(CHROMECAST_SIGNATURE);
}
private MediaRouter.Callback mMediaRouterCallback = new MediaRouter.Callback() {
#Override
public void onRouteAdded(MediaRouter router, MediaRouter.RouteInfo route) {
if (isCastDevice(route)) {
Log.i("MediaRouter", "Chromecast found: " + route);
}
}
#Override
public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo route) {
if (isCastDevice(route)) {
Log.i("MediaRouter", "Chromecast changed: " + route);
}
}
#Override
public void onRouteSelected(MediaRouter router, MediaRouter.RouteInfo route) {
if (mSelectedDevice == null && isCastDevice(route)) {
Log.i("MediaRouter", "Chromecast selected: " + route);
mSelectedDevice = CastDevice.getFromBundle(route.getExtras());
mCastClientListener = new Cast.Listener() {
#Override
public void onApplicationStatusChanged() {
Log.i("MediaRouter", "Cast.Listener.onApplicationStatusChanged()");
}
#Override
public void onApplicationMetadataChanged(ApplicationMetadata applicationMetadata) {
Log.i("MediaRouter", "Cast.Listener.onApplicationMetadataChanged(" + applicationMetadata + ")");
if (applicationMetadata != null) {
LaunchOptions launchOptions = new LaunchOptions.Builder().setRelaunchIfRunning(false).build();
Cast.CastApi.launchApplication(mApiClient, applicationMetadata.getApplicationId(), launchOptions).setResultCallback(new ResultCallback<Cast.ApplicationConnectionResult>() {
#Override
public void onResult(#NonNull Cast.ApplicationConnectionResult applicationConnectionResult) {
Log.i("MediaRouter", "Cast.CastApi.joinApplication.onResult() " + applicationConnectionResult.getSessionId());
mRemoteMediaPlayer = new RemoteMediaPlayer();
mRemoteMediaPlayer.setOnStatusUpdatedListener( new RemoteMediaPlayer.OnStatusUpdatedListener() {
#Override
public void onStatusUpdated() {
MediaStatus mediaStatus = mRemoteMediaPlayer.getMediaStatus();
Log.i("MediaRouter", "Remote media player status " + mediaStatus.getPlayerState());
// TODO: you can call isChromecastPlaying() now
}
});
try {
Cast.CastApi.setMessageReceivedCallbacks(mApiClient, mRemoteMediaPlayer.getNamespace(), mRemoteMediaPlayer);
} catch(IOException e) {
Log.e("MediaRouter", "Exception while creating media channel ", e );
} catch(NullPointerException e) {
Log.e("MediaRouter", "Something wasn't reinitialized for reconnectChannels", e);
}
mRemoteMediaPlayer.requestStatus(mApiClient).setResultCallback(new ResultCallback<RemoteMediaPlayer.MediaChannelResult>() {
#Override
public void onResult(#NonNull RemoteMediaPlayer.MediaChannelResult mediaChannelResult) {
Log.i("MediaRouter", "requestStatus() " + mediaChannelResult);
}
});
try {
Cast.CastApi.requestStatus(mApiClient);
} catch (IOException e) {
Log.e("MediaRouter", "Couldn't request status", e);
}
}
});
}
}
#Override
public void onApplicationDisconnected(int i) {
Log.i("MediaRouter", "Cast.Listener.onApplicationDisconnected(" + i + ")");
}
#Override
public void onActiveInputStateChanged(int i) {
Log.i("MediaRouter", "Cast.Listener.onActiveInputStateChanged(" + i + ")");
}
#Override
public void onStandbyStateChanged(int i) {
Log.i("MediaRouter", "Cast.Listener.onStandbyStateChanged(" + i + ")");
}
#Override
public void onVolumeChanged() {
Log.i("MediaRouter", "Cast.Listener.onVolumeChanged()");
}
};
Cast.CastOptions.Builder apiOptionsBuilder = new Cast.CastOptions.Builder(mSelectedDevice, mCastClientListener);
mApiClient = new GoogleApiClient.Builder(getContext())
.addApi( Cast.API, apiOptionsBuilder.build() )
.addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {
#Override
public void onConnected(#Nullable Bundle bundle) {
Log.i("MediaRouter", "GoogleApiClient.onConnected()");
Log.i("MediaRouter", "Bundle " + bundle);
}
#Override
public void onConnectionSuspended(int i) {
Log.i("MediaRouter", "GoogleApiClient.onConnectionSuspended(" + i + ")");
}
})
.addOnConnectionFailedListener(new GoogleApiClient.OnConnectionFailedListener() {
#Override
public void onConnectionFailed(#NonNull ConnectionResult connectionResult) {
Log.i("MediaRouter", "GoogleApiClient.onConnectionFailed()");
}
})
.build();
mApiClient.connect();
}
else {
mSelectedDevice = null;
mRemoteMediaPlayer = null;
}
}
#Override
public void onRouteRemoved(MediaRouter router, MediaRouter.RouteInfo route) {
if (isCastDevice(route)) {
if (mSelectedDevice != null && mSelectedDevice.isSameDevice(CastDevice.getFromBundle(route.getExtras()))) {
mSelectedDevice = null;
}
Log.i("MediaRouter", "Chromecast lost: " + route);
}
}
};
Related
This is my example where I am tapping on the map to get the tapped point coordinates and to send a search request to know more details about that place, for example, city, street. But the response is always null values.
public class MainActivity extends AppCompatActivity implements CameraListener, InputListener, Session.SearchListener {
private ActivityMainBinding binding;
private SearchManager searchManager;
private Session searchSession;
private SearchOptions searchOptions;
private MapObjectCollection mapObjectCollection;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MapKitFactory.setApiKey("your api key");
MapKitFactory.initialize(this);
searchOptions = new SearchOptions();
searchOptions.setSearchTypes(SearchType.GEO.value);
searchOptions.setGeometry(true);
binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
binding.mapview.getMap().setNightModeEnabled((getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES);
searchManager = SearchFactory.getInstance().createSearchManager(SearchManagerType.COMBINED);
binding.mapview.getMap().addCameraListener(this);
binding.mapview.getMap().addInputListener(this);
mapObjectCollection = binding.mapview.getMap().getMapObjects();
binding.mapview.getMap().move(
new CameraPosition(new Point(55.751574, 37.573856), 11.0f, 0.0f, 0.0f),
new Animation(Animation.Type.SMOOTH, 0),
null);
}
#Override
protected void onStop() {
super.onStop();
binding.mapview.onStop();
MapKitFactory.getInstance().onStop();
}
#Override
protected void onStart() {
super.onStart();
binding.mapview.onStart();
MapKitFactory.getInstance().onStart();
}
public void submitQueryByName(String query) {
searchSession = searchManager.submit(
query,
Geometry.fromPoint(new Point(40.177200, 44.503490)),
searchOptions,
this);
}
public void submitQueryByPoint(Point point) {
searchSession = searchManager.submit(
point,
11,
searchOptions,
this);
}
#Override
public void onCameraPositionChanged(#NonNull Map map, #NonNull CameraPosition cameraPosition, #NonNull CameraUpdateReason cameraUpdateReason, boolean finished) {
Log.e("onCameraPositionChanged"," cameraPosition: "+cameraPosition+" cameraUpdateReason: "+cameraUpdateReason+" finished: "+finished);
}
#Override
public void onMapTap(#NonNull Map map, #NonNull Point point) {
MapObjectCollection mapObjects = binding.mapview.getMap().getMapObjects();
mapObjects.clear();
PlacemarkMapObject placemarkMapObject = mapObjectCollection.addPlacemark(new Point(point.getLatitude(), point.getLongitude()),
ImageProvider.fromResource(this, R.mipmap.marker_flag));
submitQueryByPoint(point);
Log.e("onMapTap", "point lat - lang: " + point.getLatitude() + " : " + point.getLongitude());
}
#Override
public void onMapLongTap(#NonNull Map map, #NonNull Point point) {
Log.e("onMapLongTap","onMapLongTap");
}
#Override
public void onSearchResponse(#NonNull Response response) {
try {
Log.e("Search", "Response: " + response);
} catch (NullPointerException e) {
e.printStackTrace();
}
}
#Override
public void onSearchError(#NonNull Error error) {
String errorMessage = "unknown_error_message";
if (error instanceof RemoteError) {
errorMessage = "remote_error_message";
} else if (error instanceof NetworkError) {
errorMessage = "network_error_message";
}
Log.e("Response error", " error: " + errorMessage);
}
}
In the onMapTap method, I get the tapped point coordinates and send a search request by point
#Override
public void onMapTap(#NonNull Map map, #NonNull Point point) {
MapObjectCollection mapObjects = binding.mapview.getMap().getMapObjects();
mapObjects.clear();
PlacemarkMapObject placemarkMapObject = mapObjectCollection.addPlacemark(new Point(point.getLatitude(), point.getLongitude()),
ImageProvider.fromResource(this, R.mipmap.marker_flag));
submitQueryByPoint(point);
Log.e("onMapTap", "point lat - lang: " + point.getLatitude() + " : " + point.getLongitude());
}
**Response is always null values. What I do wrong?
This is a GitHub whole project for this example https://github.com/davmehrabyan/YandexMapSearch **
I am trying to integrate the Sinch API in android .
My VOIPClient. Java is like this
public class VOIPClient {
private static final String TAG = "VOIPClient";
private SinchClient mSinch;
private TTSHelper mTTS;
private Call mCurrentCall;
private BootService mContext;
private SinchClientBuilder mBuilder;
private NsdController mNsdController;
private final CallListener mCallListener = new CallListener() {
#Override
public void onCallProgressing(Call call) {
Log.d(TAG, "Call established at " + " Thusee");
}
#Override
public void onCallEstablished(Call call) {
Log.d(TAG, "Call established at " + call.getDetails().getEstablishedTime());
mTTS.speak("Call started", TTSHelper.UTTERANCE_VOIP_START);
JsonObject payload = new JsonObject();
payload.addProperty("Status", 0);
payload.addProperty("Call_status", 1);
if (mNsdController != null) {
mNsdController.sendCommand(20, payload);
}
}
#Override
public void onCallEnded(Call call) {
Log.d(TAG, "Call ended at " + call.getDetails().getEndedTime() + "caused by " + call.getDetails().getEndCause().toString());
mTTS.speak("Call ended", TTSHelper.UTTERANCE_VOIP_END);
mCurrentCall.hangup();
JsonObject payload = new JsonObject();
payload.addProperty("Status", 0);
payload.addProperty("Call_status", 1);
if (mNsdController != null) {
mNsdController.sendCommand(21, payload);
}
}
#Override
public void onShouldSendPushNotification(Call call, List<PushPair> list) {
}
};
public VOIPClient(BootService context) {
mContext = context;
mTTS = TTSHelper.getInstance(context);
mBuilder = Sinch.getSinchClientBuilder().context(mContext.getApplicationContext())
.applicationKey(CloudConfig.SINCH_APP_KEY)
.applicationSecret(CloudConfig.SINCH_APP_SECRET)
.environmentHost(CloudConfig.SINCH_ENVIRONMENT);
if (mNsdController != null)
mNsdController.initialize();
}
public void start() {
SharedPreferences prefs = mContext.getPreferences();
int userId = prefs.getInt(MerryClient.PREF_USER_ID, 0);
String mUserId;
if (userId > 0) {
mUserId = String.valueOf(userId);
mSinch = Sinch.getSinchClientBuilder().context(mContext.getApplicationContext()).userId(mUserId)
.applicationKey(CloudConfig.SINCH_APP_KEY)
.applicationSecret(CloudConfig.SINCH_APP_SECRET)
.environmentHost(CloudConfig.SINCH_ENVIRONMENT).build();
mSinch.setSupportCalling(true);
mSinch.setSupportManagedPush(false);
SinchClientListener sinchClientListener = new SinchClientListener() {
#Override
public void onClientStarted(SinchClient sinchClient) {
Log.d(TAG, "Sinch Client starts: " + sinchClient.getLocalUserId());
mTTS.speak("Call ready", TTSHelper.UTTERANCE_VOIP_READY);
}
#Override
public void onClientStopped(SinchClient sinchClient) {
Log.d(TAG, "Sinch Client stops");
}
#Override
public void onClientFailed(SinchClient sinchClient, SinchError sinchError) {
Log.e(TAG, String.format("Sinch Client error %d: %s", sinchError.getCode(), sinchError.getMessage()));
mSinch.terminate();
mTTS.speak("Voice Over IP failed", TTSHelper.UTTERANCE_VOIP_FAIL);
}
#Override
public void onRegistrationCredentialsRequired(SinchClient sinchClient, ClientRegistration clientRegistration) {
Log.d(TAG, "Sinch Client requires registration");
}
#Override
public void onLogMessage(int i, String s, String s1) {
Log.d(TAG, s1);
}
};
mSinch.addSinchClientListener(sinchClientListener);
mSinch.getCallClient().setRespectNativeCalls(false);
mSinch.getCallClient().addCallClientListener(new SinchCallClientListener());
mCurrentCall = null;
mSinch.startListeningOnActiveConnection();
mSinch.start();
}
}
public void tearDown() {
if (mSinch != null) {
mSinch.stopListeningOnActiveConnection();
mSinch.terminate();
mSinch = null;
}
}
public void restart() {
tearDown();
start();
}
public void initiateCall(final String targetUserName) {
new Thread(new Runnable() {
public void run() {
Looper.prepare();
if (targetUserName != null) {
try {
Call call = callUser(targetUserName);
call.addCallListener(mCallListener);
mCurrentCall = call;
} catch (Exception e) {
Log.e(TAG, "Initiate VOIP call failed", e);
}
}
Looper.loop();
}
}).start();
}
public void answerCall() {
if (mCurrentCall != null) {
mCurrentCall.answer();
}
}
public void hangUpCall() {
if (mCurrentCall != null) {
mCurrentCall.hangup();
}
}
private class SinchCallClientListener implements CallClientListener {
#Override
public void onIncomingCall(CallClient callClient, Call call) {
Log.d(TAG, "Incoming call");
mTTS.speak("Incoming call from " + call.getRemoteUserId(), TTSHelper.UTTERANCE_VOIP_INCOMING);
call.addCallListener(mCallListener);
mCurrentCall = call;
// For testing only
answerCall();
}
}
public Call callUser(String userId) {
if (mSinch != null && mSinch.isStarted()) {
start();
}
if (mSinch == null) {
return null;
}
return mSinch.getCallClient().callUser(userId);
}
class CallerThread implements Runnable {
public String mtargetUserName;
CallerThread(String targetUserName) {
this.mtargetUserName = targetUserName;
}
#Override
public void run() {
Looper.prepare();
if (mtargetUserName != null) {
try {
Call call = callUser(mtargetUserName);
call.addCallListener(mCallListener);
mCurrentCall = call;
} catch (Exception e) {
Log.e(TAG, "Initiate VOIP call failed", e);
mContext.getAlexa().start();
}
}
Looper.loop();
}
}
}
When I try to call to other device then I am getting these kind of exceptions
Initiate VOIP call failed
java.lang.IllegalStateException: SinchClient not started
at com.sinch.android.rtc.internal.client.calling.DefaultCallClient.throwUnlessStarted(Unknown Source)
at com.sinch.android.rtc.internal.client.calling.DefaultCallClient.call(Unknown Source)
at com.sinch.android.rtc.internal.client.calling.DefaultCallClient.callUser(Unknown Source)
at com.sinch.android.rtc.internal.client.calling.DefaultCallClient.callUser(Unknown Source)
at tw.com.test.cloud.VOIPClient.callUser(VOIPClient.java:272)
at tw.com.test.cloud.VOIPClient$CallerThread.run(VOIPClient.java:293)
at java.lang.Thread.run(Thread.java:818)
Also some times I am getting this exception
FATAL EXCEPTION: Thread-75
Process: tw.com.test.wear, PID: 1123
java.lang.IllegalThreadStateException: A Looper must be associated with this thread.
at com.sinch.android.rtc.internal.AndroidLooperCallbackHandler.<init>(Unknown Source)
at com.sinch.android.rtc.internal.client.InternalSinchClientFactory.createSinchClient(Unknown Source)
at com.sinch.android.rtc.DefaultSinchClientBuilder.build(Unknown Source)
at tw.com.test.cloud.VOIPClient.start(VOIPClient.java:109)
at tw.com.test.cloud.VOIPClient$2.onClientStopped(VOIPClient.java:124)
at com.sinch.android.rtc.internal.client.DefaultSinchClient.shutdown(Unknown Source)
at com.sinch.android.rtc.internal.client.DefaultSinchClient.terminate(Unknown Source)
at tw.com.test.cloud.VOIPClient.tearDown(VOIPClient.java:160)
at tw.com.test.nsd.NsdController.messageReceived(NsdController.java:570)
at tw.com.test.nsd.NsdConnection.run(NsdConnection.java:115)
at java.lang.Thread.run(Thread.java:818)
2 Days I tried my self, I can't able to solve this yet,
I am always getting these exception. Sometimes it will work for one time then I need to restart the app.
You have to start SinchClient sinchClient.start(); and take NOTE during production mode do not place in a plain text form your SINCH_APP_SECRET, because its a secret key, hackers will easily read or decompile your code.
public VOIPClient(BootService context) {
mContext = context;
mTTS = TTSHelper.getInstance(context);
mBuilder = Sinch.getSinchClientBuilder().context(mContext.getApplicationContext())
.applicationKey(CloudConfig.SINCH_APP_KEY)
.applicationSecret(CloudConfig.SINCH_APP_SECRET)
.environmentHost(CloudConfig.SINCH_ENVIRONMENT);
sinchClient.setSupportCalling(true);
sinchClient.start();
if (mNsdController != null)
mNsdController.initialize();
}
i can connect ios app with kurento room for conference call without any issue but i cannot connect it with android, here i am following tutorial Kurento WebRTC Peer For Android to make android client to connect with kurento room.
Here is the code what i am trying
public class MainActivity extends AppCompatActivity implements
RoomListener,NBMWebRTCPeer.Observer {
private LooperExecutor executor;
private static KurentoRoomAPI kurentoRoomAPI;
private EglBase rootEglBase;
private NBMWebRTCPeer nbmWebRTCPeer;
private SurfaceViewRenderer localView;
private SurfaceViewRenderer remoteView;
private VideoRenderer.Callbacks localRender;
private SessionDescription localSdp;
private SessionDescription remoteSdp;
NBMMediaConfiguration.NBMVideoFormat receiverVideoFormat = new NBMMediaConfiguration.NBMVideoFormat(1280, 720, ImageFormat.YUV_420_888, 30);
NBMMediaConfiguration mediaConfiguration = new NBMMediaConfiguration(NBMMediaConfiguration.NBMRendererType.OPENGLES, NBMMediaConfiguration.NBMAudioCodec.OPUS, 0, NBMMediaConfiguration.NBMVideoCodec.VP8, 0, receiverVideoFormat, NBMMediaConfiguration.NBMCameraPosition.FRONT);
private boolean isMyVideoPublished = false;
private boolean isMyIceCandidateSent = false;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
int hasCameraPermission = checkSelfPermission(Manifest.permission.CAMERA);
List<String> permissions = new ArrayList<String>();
if (hasCameraPermission != PackageManager.PERMISSION_GRANTED) {
permissions.add(Manifest.permission.CAMERA);
}
if (!permissions.isEmpty()) {
requestPermissions(permissions.toArray(new String[permissions.size()]), 111);
}
}
executor = new LooperExecutor();
executor.requestStart();
String wsRoomUri = "wss://172.16.1.9:8443/room";
kurentoRoomAPI = new KurentoRoomAPI(executor, wsRoomUri, this);
kurentoRoomAPI.connectWebSocket();
localView = (SurfaceViewRenderer) findViewById(R.id.gl_surface_local);
remoteView = (SurfaceViewRenderer) findViewById(R.id.gl_surface_remote);
localView.init(EglBase.create().getEglBaseContext(), null);
localView.setZOrderMediaOverlay(true);
localView.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL);
localView.setMirror(true);
localView.requestLayout();
remoteView.init(EglBase.create().getEglBaseContext(), null);
remoteView.setZOrderMediaOverlay(true);
remoteView.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL);
remoteView.setMirror(true);
remoteView.requestLayout();
nbmWebRTCPeer = new NBMWebRTCPeer(mediaConfiguration, this, localView, this);
nbmWebRTCPeer.initialize();
//nbmWebRTCPeer.generateOffer("local", true);
//nbmWebRTCPeer.generateOffer("MyRemotePeer", false);
}
#Override
public void onRoomResponse(RoomResponse response) {
Log.d("onRoomResponse", "******** onRoomResponse : "+response.toString());
if (response.getId() == 123) {
Log.d("onRoomResponse", "Successfully connected to the room!");
nbmWebRTCPeer.generateOffer("Jeeva", true);
//kurentoRoomAPI.sendMessage("112233", "Jeeva Moto", "Hello room!", 125);
} else if (response.getId() == 125) {
Log.d("onRoomResponse", "The server received my message!");
} else if (response.getId() == 126) {
}else if (response.getId() == 129) {
}
}
#Override
public void onRoomError(RoomError error) {
Log.d("onRoomError", "******** onRoomError : "+error.toString());
}
#Override
public void onRoomNotification(RoomNotification notification) {
if(notification.getMethod().equals(RoomListener.METHOD_SEND_MESSAGE)) {
final String username = notification.getParam("user").toString();
final String message = notification.getParam("message").toString();
Log.d("onRoomNotification", "Oh boy! " + username + " sent me a message: " + message);
}
Log.d("onRoomNotification", "******** RoomNotification : " + notification);
if(notification.getMethod().equals("iceCandidate"))
{
Map<String, Object> map = notification.getParams();
Log.d("onRoomNotification", "******** Map RoomNotification : " + map);
String sdpMid = map.get("sdpMid").toString();
int sdpMLineIndex = Integer.valueOf(map.get("sdpMLineIndex").toString());
String sdp = map.get("candidate").toString();
IceCandidate ic = new IceCandidate(sdpMid, sdpMLineIndex, sdp);
nbmWebRTCPeer.addRemoteIceCandidate(ic, "remote");
}
}
#Override
public void onRoomConnected() {
Log.d("onRoomConnected","******** Called");
kurentoRoomAPI.sendJoinRoom("Jeeva", "112233", true, 123);
}
#Override
public void onRoomDisconnected() {
}
#Override
public void onInitialize() {
}
#Override
public void onLocalSdpOfferGenerated(SessionDescription localSdpOffer, NBMPeerConnection connection) {
Log.d("onLclSdpOfrGen","******** localSdpOffer : "+localSdpOffer.description+" connection : "+connection.getConnectionId());
if (!isMyVideoPublished) {
kurentoRoomAPI.sendPublishVideo(localSdpOffer.description,false,129);
//String username = "qwerty";
//kurentoRoomAPI.sendReceiveVideoFrom(username, "webcam", localSdpOffer.description, 129);
isMyVideoPublished = true;
}else {
String username = "qwerty";
kurentoRoomAPI.sendReceiveVideoFrom(username, "webcam", localSdpOffer.description, 129);
}
}
#Override
public void onLocalSdpAnswerGenerated(SessionDescription localSdpAnswer, NBMPeerConnection connection) {
Log.d("onLclSdpAnsGen","******** localSdpAnswer : "+localSdpAnswer.description+" connection : "+connection.getConnectionId());
}
#Override
public void onIceCandidate(IceCandidate iceCandidate, NBMPeerConnection nbmPeerConnection) {
Log.d("onIceCandidate", "******** iceCandidate : " + iceCandidate.sdp + " nbmPeerConnection : " + nbmPeerConnection.getConnectionId());
if (!isMyIceCandidateSent){
isMyIceCandidateSent = true;
kurentoRoomAPI.sendOnIceCandidate("Jeeva", iceCandidate.sdp, iceCandidate.sdpMid, Integer.toString(iceCandidate.sdpMLineIndex),129);
} else {
kurentoRoomAPI.sendOnIceCandidate("qwerty", iceCandidate.sdp,
iceCandidate.sdpMid, Integer.toString(iceCandidate.sdpMLineIndex), 129);
nbmWebRTCPeer.addRemoteIceCandidate(iceCandidate, iceCandidate.sdp);
}
}
#Override
public void onIceStatusChanged(PeerConnection.IceConnectionState state, NBMPeerConnection connection) {
Log.d("onIceStatusChanged","******** state : "+state+" connection : "+connection);
}
#Override
public void onRemoteStreamAdded(MediaStream stream, NBMPeerConnection connection) {
Log.d("onRemoteStreamAdded","******** stream : "+stream+" connection : "+connection);
nbmWebRTCPeer.attachRendererToRemoteStream(remoteView, stream);
}
#Override
public void onRemoteStreamRemoved(MediaStream stream, NBMPeerConnection connection) {
Log.d("onRemoteStreamRemoved","******** stream : "+stream+" connection : "+connection);
}
#Override
public void onPeerConnectionError(String error) {
Log.d("onPeerConnectionError","******** error : "+error);
}
#Override
public void onDataChannel(DataChannel dataChannel, NBMPeerConnection connection) {
Log.d("onDataChannel","******** dataChannel : "+dataChannel+" connection : "+connection);
}
#Override
public void onBufferedAmountChange(long l, NBMPeerConnection connection, DataChannel channel) {
Log.d("onBufferedAmountChange","******** channel : "+channel+" connection : "+connection);
}
#Override
public void onStateChange(NBMPeerConnection connection, DataChannel channel) {
Log.d("onStateChange","******** channel : "+channel+" connection : "+connection);
}
#Override
public void onMessage(DataChannel.Buffer buffer, NBMPeerConnection connection, DataChannel channel) {
Log.d("onMessage","******** channel : "+channel+" buffer : "+buffer+" connection : "+connection);
}}
I am looking for any working sample for android client which connects kurento room for conference call.
Kurento also has completed Android client with simple UI.
It allows to connect to "kurento-room" server using of their KurentoAPI.
It has only 2p2, but contain implementation of all needed signaling, so it may be a good start point.
More details are there.
i am developing a streaming app in which i have implemented chromecast functionality.Everything is working fine but the only issue is when a content video have pre roll ad then either ad is skipped and content video is played if we connected with chromecast or ad is locally played and after that content video is played through chromecast.My question is can we play both ad and content video through chromecast using IMA sdk because manually it is possible and i have done this but with IMA sdk there no luck.My code is as :
public class ClassChromeCast {
Context context;
public ClassChromeCast(Context ctx){
context = ctx;
}
/**
* Chromecast
* */
private static final String TAG = "MainActivity";
public MediaRouter mMediaRouter;
public static MediaRouteSelector mMediaRouteSelector;
public MediaRouter.Callback mMediaRouterCallback;
private CastDevice mSelectedDevice;
private GoogleApiClient mApiClient;
private RemoteMediaPlayer mRemoteMediaPlayer;
private Cast.Listener mCastClientListener;
public static boolean mIsPlaying,mIsFinished,mApplicationStarted = false,mVideoIsLoaded,isConnected = false;
private boolean mWaitingForReconnect = false,temp = false,temp2 = false;
/**
* Local variable
*/
private static String videoUrl = "",videoTitle = "" ,videoAdUrl = "";
private RSSFeed_Addsplay rssFeed_AddsplayObj;
/*======================Chromecast=======================*/
public void initMediaRouter() {
// Configure Cast device discovery
mMediaRouter = MediaRouter.getInstance(context);
mMediaRouteSelector = new MediaRouteSelector.Builder()
.addControlCategory( CastMediaControlIntent.categoryForCast( context.getString( R.string.app_id ) ) )
.build();
mMediaRouterCallback = new MediaRouterCallback();
}
/**inner class MediaRouterCallback **/
private class MediaRouterCallback extends MediaRouter.Callback {
#Override
public void onRouteSelected(MediaRouter router, MediaRouter.RouteInfo info) {
Log.d(TAG, "onRouteSelected");
initCastClientListener();
initRemoteMediaPlayer();
mSelectedDevice = CastDevice.getFromBundle( info.getExtras() );
launchReceiver();
}
#Override
public void onRouteUnselected( MediaRouter router, MediaRouter.RouteInfo info ) {
Log.d(TAG, "onRouteUnselected");
//teardown();
mSelectedDevice = null;
//isConnected = false;
//mVideoIsLoaded = false;
}
}
/**---------------to initialize CastClientListener---------------*/
private void initCastClientListener() {
mCastClientListener = new Cast.Listener() {
#Override
public void onApplicationStatusChanged() {
Log.d(TAG, "onApplicationStatusChanged");
}
#Override
public void onVolumeChanged() {
Log.d(TAG, "onVolumeChanged");
}
#Override
public void onApplicationDisconnected( int statusCode ) {
Log.d(TAG, "onApplicationDisconnected");
teardown();
}
};
}
/**---------------to initialize RemoteMediaPlayer---------------*/
private void initRemoteMediaPlayer() {
mRemoteMediaPlayer = new RemoteMediaPlayer();
mRemoteMediaPlayer.setOnStatusUpdatedListener( new RemoteMediaPlayer.OnStatusUpdatedListener() {
#Override
public void onStatusUpdated() {
try{
MediaStatus mediaStatus = mRemoteMediaPlayer.getMediaStatus();
mIsPlaying = mediaStatus.getPlayerState() == MediaStatus.PLAYER_STATE_PLAYING;
mIsFinished = mediaStatus.getPlayerState() == MediaStatus.PLAYER_STATE_IDLE;
Log.d(TAG,"IsPlaying==="+mIsPlaying+"IsFinished===="+mIsFinished);
if(mIsFinished && temp && !temp2){
//temp = false;
Log.e("startVideo2","===");
startVideo(videoUrl,videoTitle);
temp2 = true;
}
if(mIsPlaying){
temp = true;
}
}catch (Exception e){
e.printStackTrace();
}
}
});
mRemoteMediaPlayer.setOnMetadataUpdatedListener( new RemoteMediaPlayer.OnMetadataUpdatedListener() {
#Override
public void onMetadataUpdated() {
Log.d(TAG,"====mIsFinished");
}
});
}
private void launchReceiver() {
Cast.CastOptions.Builder apiOptionsBuilder = Cast.CastOptions
.builder( mSelectedDevice, mCastClientListener );
GoogleApiClient.ConnectionCallbacks mConnectionCallbacks = new GoogleApiClient.ConnectionCallbacks() {
#Override
public void onConnected(Bundle bundle) {
Log.d(TAG,"Connected");
isConnected = true;
if( mWaitingForReconnect ) {
mWaitingForReconnect = false;
reconnectChannels( bundle );
} else {
try {
Cast.CastApi.launchApplication(mApiClient,context.getString(R.string.app_id), false)
.setResultCallback(new ResultCallback<Cast.ApplicationConnectionResult>() {
#Override
public void onResult(Cast.ApplicationConnectionResult applicationConnectionResult) {
Status status = applicationConnectionResult.getStatus();
if (status.isSuccess()) {
mApplicationStarted = true;
reconnectChannels(null);
}
}
}
);
} catch (Exception e) {
e.printStackTrace();
}
}
}
#Override
public void onConnectionSuspended(int i) {
Log.e(TAG,"onConnectionSuspended");
isConnected = false;
mWaitingForReconnect = true;
}
};
GoogleApiClient.OnConnectionFailedListener mConnectionFailedListener = new GoogleApiClient.OnConnectionFailedListener() {
#Override
public void onConnectionFailed(ConnectionResult connectionResult) {
Log.d(TAG, "onConnectionFailed");
isConnected = false;
teardown();
}
};
mApiClient = new GoogleApiClient.Builder(context)
.addApi( Cast.API, apiOptionsBuilder.build() )
.addConnectionCallbacks( mConnectionCallbacks )
.addOnConnectionFailedListener( mConnectionFailedListener )
.build();
mApiClient.connect();
}
private void reconnectChannels( Bundle hint ) {
if( ( hint != null ) && hint.getBoolean( Cast.EXTRA_APP_NO_LONGER_RUNNING ) ) {
Log.e( TAG, "App is no longer running" );
Log.d(TAG, "reconnectChannels");
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" );
}catch (IllegalStateException e){
e.printStackTrace();
}
}
}
public void teardown() {
if( mApiClient != null ) {
if( mApplicationStarted ){
try {
Cast.CastApi.stopApplication( mApiClient );
if( mRemoteMediaPlayer != null ) {
Cast.CastApi.removeMessageReceivedCallbacks( mApiClient, mRemoteMediaPlayer.getNamespace() );
mRemoteMediaPlayer = null;
}
} catch( Exception e ) {
Log.e( TAG, "Exception while removing application " + e );
}
mApplicationStarted = false;
}
if( mApiClient.isConnected() )
mApiClient.disconnect();
mApiClient = null;
}
mSelectedDevice = null;
mVideoIsLoaded = false;
}
/**----to play video remotely-----*/
public void playOnRemoteMediaPlayer(String title,String url, String adUrl){
Log.e("OnRemoteMediaPlayer","adUrl=="+adUrl);
try{
rssFeed_AddsplayObj = new RSSFeed_Addsplay(adUrl + System.currentTimeMillis());
videoAdUrl = rssFeed_AddsplayObj.getAdUrl();
}catch (Exception e){
videoAdUrl = "";
}
if(!mVideoIsLoaded && !videoUrl.equals(url)){
videoUrl = url;
videoTitle = title;
temp2 = false;
temp = false;
if (videoAdUrl != null && videoAdUrl.length() > 0){
Log.e("IF","===");
startVideo(videoAdUrl,"Your program begin after this ad break");
} else {
Log.e("ELSE","===");
startVideo(videoUrl,videoTitle);
}
//playPauseBtn.setImageResource(R.drawable.ic_av_pause_over_video_large);
} else {
controlVideo(videoUrl,videoTitle);
if(mIsPlaying){
//playPauseBtn.setImageResource(R.drawable.ic_av_play_over_video_large);
}else {
//playPauseBtn.setImageResource(R.drawable.ic_av_pause_over_video_large);
}
}
}
private void startVideo(String url, String s) {
Log.e(TAG,"===startVideo"+url);
TVE_HomeActivity.tvVideoLoadingText.setText(context.getString(R.string.playtext));
MediaMetadata mediaMetadata = new MediaMetadata( MediaMetadata.MEDIA_TYPE_MOVIE );
mediaMetadata.putString( MediaMetadata.KEY_TITLE,s);
MediaInfo mediaInfo = new MediaInfo.Builder(url)
.setContentType("video/mp4")
.setStreamType( MediaInfo.STREAM_TYPE_BUFFERED )
.setMetadata( mediaMetadata )
.build();
try {
mRemoteMediaPlayer.load( mApiClient, mediaInfo, true )
.setResultCallback( new ResultCallback<RemoteMediaPlayer.MediaChannelResult>() {
#Override
public void onResult( RemoteMediaPlayer.MediaChannelResult mediaChannelResult ) {
if( mediaChannelResult.getStatus().isSuccess() ) {
mVideoIsLoaded = true;
}
}
});
} catch( Exception e ) {
isConnected = false;
e.printStackTrace();
}
}
private void controlVideo(String url, String title) {
TVE_HomeActivity.tvVideoLoadingText.setText(context.getString(R.string.pausetext));
try{
if( mRemoteMediaPlayer == null || !mVideoIsLoaded )
return;
if( mIsPlaying ) {
mRemoteMediaPlayer.pause( mApiClient );
} else if(mIsFinished){
Log.d(TAG,"Finished===true");
startVideo(url,title);
} else {
mRemoteMediaPlayer.play( mApiClient );
}
Log.d(TAG,"running==="+mIsFinished);
}catch (Exception e){
e.printStackTrace();
}
}
}
I have strange issue, I am creating mediaprovider for chromecast using following code that works fine for first instance, list of devices is shown and once slected I use router.selectRoute(routeinfo);
but once I exit app this code unable to find Chromecast device, how ever when I remove app from running apps stack this code works fine again and show devices.
If no device is selected and app is exited using back press then also this code works fine
So what I am doing wrong here? what I think is resources are not cleared when my app exit in simple back pressed.
public class ChromecastRouteProviderService extends MediaRouteProviderService {
final String LOGTAG = "Chromecast";
private static final String CONTROL_CATEGORY = CastMediaControlIntent.categoryForCast(CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID);
private static final MediaRouteSelector SELECTOR = new MediaRouteSelector.Builder().addControlCategory(CONTROL_CATEGORY)
.addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK).build();
private IntentFilter controlFilter;
public ChromecastRouteProviderService() {
controlFilter = new IntentFilter();
}
public void onCreate() {
super.onCreate();
controlFilter.addCategory(IAppConstants.CATEGORY);
controlFilter.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
}
#Override
public MediaRouteProvider onCreateMediaRouteProvider() {
return new ChromecastRouteProvider(this);
}
class ChromecastRouteProvider extends MediaRouteProvider {
MediaRouter.Callback callback;
Hashtable routes;
public ChromecastRouteProvider(Context context) {
super(context);
routes = new Hashtable();
callback = new CastCallBack();
}
#Nullable
#Override
public RouteController onCreateRouteController(String routeId) {
MediaRouter.RouteInfo routeInfo = (MediaRouter.RouteInfo) routes.get(routeId);
if (routeInfo == null) {
return super.onCreateRouteController(routeId);
} else {
return new ChromecastRouteController(getContext(), routeInfo);
}
}
#Override
public void onDiscoveryRequestChanged(#Nullable MediaRouteDiscoveryRequest request) {
super.onDiscoveryRequestChanged(request);
if (request == null || !request.isActiveScan() || !request.isValid()) {
stopScan();
return;
}
if (!request.getSelector().hasControlCategory(IAppConstants.CATEGORY)) {
Log.i(LOGTAG, "Not scanning for non remote playback");
stopScan();
return;
} else {
Log.i(LOGTAG, "Scanning...");
mediarouter.addCallback(ChromecastRouteProviderService.SELECTOR, callback, MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
return;
}
}
void updateDescriptor() {
final MediaRouteProviderDescriptor.Builder descriptor = new MediaRouteProviderDescriptor.Builder();
for (Iterator iterator = routes.values().iterator(); iterator.hasNext(); ) {
MediaRouter.RouteInfo routeinfo = (MediaRouter.RouteInfo) iterator.next();
try {
Bundle bundle = new Bundle();
bundle.putBoolean("has_upsell", true);
descriptor.addRoute(new MediaRouteDescriptor.Builder(routeinfo.getId(), routeinfo.getName())
.addControlFilter(controlFilter).setPlaybackStream(3)
.setDescription(routeinfo.getDescription())
.setEnabled(true).setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE)
.setVolumeHandling(1).setVolumeMax(100).setVolume(100)
.setExtras(bundle).build());
} catch (Exception e) {
throw new Error("wtf");
}
}
getHandler().post(new Runnable() {
#Override
public void run() {
setDescriptor(descriptor.build());
}
});
}
void stopScan() {
Log.i(LOGTAG, "Stopping scan...");
try {
MediaRouter.getInstance(getContext()).removeCallback(callback);
return;
} catch (Exception exception) {
return;
}
}
class CastCallBack extends MediaRouter.Callback {
void check(MediaRouter mediarouter, MediaRouter.RouteInfo routeinfo) {
Log.i(LOGTAG, new StringBuilder().append("Checking route ").append
(routeinfo.getName()).toString());
CastDevice device = CastDevice.getFromBundle(routeinfo.getExtras());
if (routeinfo.matchesSelector(ChromecastRouteProviderService.SELECTOR)
&& device != null && device.isOnLocalNetwork()) {
routes.put(routeinfo.getId(), routeinfo);
updateDescriptor();
return;
} else {
return;
}
}
public void onRouteAdded(MediaRouter mediarouter, MediaRouter.RouteInfo routeinfo) {
super.onRouteAdded(mediarouter, routeinfo);
check(mediarouter, routeinfo);
}
public void onRouteChanged(MediaRouter mediarouter, MediaRouter.RouteInfo routeinfo) {
super.onRouteChanged(mediarouter, routeinfo);
check(mediarouter, routeinfo);
}
public void onRouteRemoved(MediaRouter mediarouter, MediaRouter.RouteInfo routeinfo) {
super.onRouteRemoved(mediarouter, routeinfo);
if (routeinfo.matchesSelector(ChromecastRouteProviderService.SELECTOR)) ;
}
}
}
}
Ok finally I found answer on my own,
Problem is when any provider is selected it's not added using onRouteAdded why? I really dont understand google logic
So the solution is to unselect the router when you want or better select default route when so that your route is released
MediaRouter.getInstance(this).getDefaultRoute().select();
But again 1 out of 10 times it will not work
Hope will help someone