From 84c7f555c78ada1b72c83a5f1fe1f4ba195fa534 Mon Sep 17 00:00:00 2001 From: Antonis Tsakiridis Date: Mon, 6 Jul 2015 16:46:45 +0300 Subject: [PATCH] Fixes #68: Add workaround for incoming media, so that the MS announcements aren't truncated. Fixes #64: Crash when reopening the App if it was closed with 'back' button. Fixes #69: Add Audio Focus in the activity as well --- .../restcomm_messenger/MainActivity.java | 100 ++++++++++++++++-- .../restcomm/android/client/sdk/RCClient.java | 2 +- .../android/client/sdk/RCConnection.java | 5 +- .../restcomm/android/client/sdk/RCDevice.java | 56 +++++----- .../android/sipua/impl/SoundManager.java | 98 +++++++++-------- .../sipua/impl/sipmessages/Invite.java | 20 ++-- 6 files changed, 189 insertions(+), 92 deletions(-) diff --git a/Examples/restcomm-messenger/app/src/main/java/com/telestax/restcomm_messenger/MainActivity.java b/Examples/restcomm-messenger/app/src/main/java/com/telestax/restcomm_messenger/MainActivity.java index 9c7773aa..cb3ab946 100644 --- a/Examples/restcomm-messenger/app/src/main/java/com/telestax/restcomm_messenger/MainActivity.java +++ b/Examples/restcomm-messenger/app/src/main/java/com/telestax/restcomm_messenger/MainActivity.java @@ -3,10 +3,12 @@ //import android.support.v7.app.ActionBarActivity; import android.app.Activity; import android.app.AlertDialog; +import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Configuration; +import android.media.AudioManager; import android.media.MediaPlayer; import android.os.Bundle; import android.preference.PreferenceManager; @@ -29,12 +31,13 @@ import org.mobicents.restcomm.android.client.sdk.RCDeviceListener; import org.mobicents.restcomm.android.client.sdk.RCPresenceEvent; +import java.util.ArrayList; import java.util.HashMap; public class MainActivity extends Activity implements RCDeviceListener, RCConnectionListener, OnClickListener, SharedPreferences.OnSharedPreferenceChangeListener, OnCheckedChangeListener, - MediaPlayer.OnPreparedListener { + MediaPlayer.OnPreparedListener, AudioManager.OnAudioFocusChangeListener { SharedPreferences prefs; private RCDevice device; @@ -44,6 +47,7 @@ public class MainActivity extends Activity implements RCDeviceListener, RCConnec MediaPlayer ringingPlayer; MediaPlayer callingPlayer; MediaPlayer messagePlayer; + AudioManager audioManager; // UI elements Button btnRegister; @@ -115,14 +119,21 @@ public void onError(Exception exception) cbMuted.setEnabled(false); + // volume control should be by default 'music' which will control the ringing sounds and 'voice call' when within a call + setVolumeControlStream(AudioManager.STREAM_MUSIC); // Setup Media (notice that I'm not preparing the media as create does that implicitly plus // I'm not ever stopping a player -instead I'm pausing so no additional preparation is needed // there either. We might need to revisit this at some point though ringingPlayer = MediaPlayer.create(getApplicationContext(), R.raw.ringing); + ringingPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); ringingPlayer.setLooping(true); callingPlayer = MediaPlayer.create(getApplicationContext(), R.raw.calling); + callingPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); callingPlayer.setLooping(true); messagePlayer = MediaPlayer.create(getApplicationContext(), R.raw.message); + messagePlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); + + audioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE); } @Override @@ -168,12 +179,16 @@ public void onClick(View view) { pendingConnection.accept(); connection = this.pendingConnection; ringingPlayer.pause(); + // Abandon audio focus when playback complete + audioManager.abandonAudioFocus(this); } } else if (view.getId() == R.id.button_decline) { if (pendingConnection != null) { pendingConnection.reject(); pendingConnection = null; ringingPlayer.pause(); + // Abandon audio focus when playback complete + audioManager.abandonAudioFocus(this); } } else if (view.getId() == R.id.button_cancel) { if (connection == null) { @@ -184,6 +199,8 @@ public void onClick(View view) { connection = null; pendingConnection = null; callingPlayer.pause(); + // Abandon audio focus when playback complete + audioManager.abandonAudioFocus(this); } } else if (view.getId() == R.id.button_send) { HashMap sendParams = new HashMap(); @@ -197,7 +214,10 @@ public void onClick(View view) { */ txtWall.append("Me: " + txtMessage.getText().toString() + "\n"); - messagePlayer.start(); + int result = audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT); + if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { + messagePlayer.start(); + } } } } @@ -248,13 +268,20 @@ public void onPresenceChanged(RCDevice device, RCPresenceEvent presenceEvent) public void onIncomingConnection(RCDevice device, RCConnection connection) { Log.i(TAG, "Connection arrived"); - ringingPlayer.start(); + int result = audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT); + if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { + ringingPlayer.start(); + } pendingConnection = connection; } public void onIncomingMessage(RCDevice device, String message, HashMap parameters) { Log.i(TAG, "Message arrived: " + message); + int result = audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT); + if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { + messagePlayer.start(); + } /* put new text on top String text = txtWall.getText().toString(); String newText = parameters.get("username") + ": " + message + "\n" + text; @@ -262,15 +289,16 @@ public void onIncomingMessage(RCDevice device, String message, HashMap list = RCClient.getInstance().listDevices(); + if (list.size() != 0) { + RCDevice device = list.get(0); + RCConnection pendingConnection = device.getPendingConnection(); + onIncomingConnection(device, pendingConnection); + } + } + if (intent.getAction() == "ACTION_INCOMING_MESSAGE") { + ArrayList list = RCClient.getInstance().listDevices(); + if (list.size() != 0) { + RCDevice device = list.get(0); + RCConnection pendingConnection = device.getPendingConnection(); + HashMap parms = (HashMap)intent.getSerializableExtra("MESSAGE_PARMS"); + String message = (String)intent.getSerializableExtra("MESSAGE"); + onIncomingMessage(device, message, parms); + } + } } @Override protected void onPause() { @@ -433,4 +490,27 @@ protected void onDestroy() { // The activity is about to be destroyed. Log.i(TAG, "%% onDestroy"); } + + // Callbacks for auio focus change events + public void onAudioFocusChange(int focusChange) + { + Log.i(TAG, "onAudioFocusChange: " + focusChange); + /* + if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) { + // Pause playback + } + else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) { + // Resume playback or raise it back to normal if we were ducked + } + else if (focusChange == AudioManager.AUDIOFOCUS_LOSS) { + //am.unregisterMediaButtonEventReceiver(RemoteControlReceiver); + audio.abandonAudioFocus(this); + // Stop playback + } + else if (focusChange == AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) { + // Lower the volume + } + */ + } + } diff --git a/restcomm.android.client.sdk/src/main/java/org/mobicents/restcomm/android/client/sdk/RCClient.java b/restcomm.android.client.sdk/src/main/java/org/mobicents/restcomm/android/client/sdk/RCClient.java index 5513765b..46786430 100644 --- a/restcomm.android.client.sdk/src/main/java/org/mobicents/restcomm/android/client/sdk/RCClient.java +++ b/restcomm.android.client.sdk/src/main/java/org/mobicents/restcomm/android/client/sdk/RCClient.java @@ -136,7 +136,7 @@ public static RCDevice createDevice(String capabilityToken, RCDeviceListener dev * Retrieve a list of active Devices * @return List of Devices */ - public List listDevices() + public ArrayList listDevices() { //ArrayList list = new ArrayList(); return list; diff --git a/restcomm.android.client.sdk/src/main/java/org/mobicents/restcomm/android/client/sdk/RCConnection.java b/restcomm.android.client.sdk/src/main/java/org/mobicents/restcomm/android/client/sdk/RCConnection.java index 1ae36842..e770f2d8 100644 --- a/restcomm.android.client.sdk/src/main/java/org/mobicents/restcomm/android/client/sdk/RCConnection.java +++ b/restcomm.android.client.sdk/src/main/java/org/mobicents/restcomm/android/client/sdk/RCConnection.java @@ -50,7 +50,7 @@ * Once an RCConnection (either incoming or outgoing) is established (i.e. RCConnectionStateConnected) media can start flowing over it. DTMF digits can be sent over to * the remote party using RCConnection.sendDigits() (Not implemented yet). When done with the RCConnection you can disconnect it with RCConnection.disconnect(). */ -public class RCConnection implements SipUAConnectionListener, Parcelable { +public class RCConnection implements SipUAConnectionListener { /** * Connection State */ @@ -329,6 +329,8 @@ private boolean haveConnectivity() } } + // Parcelable stuff (not needed for now -let's keep around in case we use it at some point): + /* @Override public int describeContents() { return 0; @@ -358,4 +360,5 @@ private RCConnection(Parcel in) { in.readBooleanArray(one); incoming = one[0]; } + */ } diff --git a/restcomm.android.client.sdk/src/main/java/org/mobicents/restcomm/android/client/sdk/RCDevice.java b/restcomm.android.client.sdk/src/main/java/org/mobicents/restcomm/android/client/sdk/RCDevice.java index 0ac634cb..725d8ee0 100644 --- a/restcomm.android.client.sdk/src/main/java/org/mobicents/restcomm/android/client/sdk/RCDevice.java +++ b/restcomm.android.client.sdk/src/main/java/org/mobicents/restcomm/android/client/sdk/RCDevice.java @@ -52,7 +52,7 @@ * @see RCConnection */ -public class RCDevice implements SipUADeviceListener, Parcelable { +public class RCDevice implements SipUADeviceListener { /** * @abstract Device state (Not Implemented yet: device is always READY) */ @@ -268,9 +268,20 @@ public void setIncomingIntent(Intent intent) { //intent.putExtra(EXTRA_DEVICE, this); //intent.putExtra(EXTRA_CONNECTION, this); + //intent.setAction("ACTION_INCOMING_CALL"); pendingIntent = PendingIntent.getActivity(RCClient.getInstance().context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); } + public RCConnection getPendingConnection() + { + return (RCConnection)DeviceImpl.GetInstance().sipuaConnectionListener; + } + + public RCConnection getDevice() + { + return (RCConnection)DeviceImpl.GetInstance().sipuaConnectionListener; + } + /** * Should a ringing sound be played in a incoming connection or message * @param incomingSound Whether or not the sound should be played @@ -354,40 +365,24 @@ public void onSipUAConnectionArrived(SipEvent event) connection.state = RCConnection.ConnectionState.CONNECTING; DeviceImpl.GetInstance().sipuaConnectionListener = connection; - /**/ - final RCConnection finalConnection = new RCConnection(connection); - final RCDevice finalDevice = new RCDevice(this); - // Important: need to fire the event in UI context cause currently we 're in JAIN SIP thread Handler mainHandler = new Handler(RCClient.getInstance().context.getMainLooper()); Runnable myRunnable = new Runnable() { @Override public void run() { - /* // bring the App to front try { - pendingIntent.send(); + Intent dataIntent = new Intent(); + dataIntent.setAction("ACTION_INCOMING_CALL"); + pendingIntent.send(RCClient.getInstance().context, 0, dataIntent); + } catch (PendingIntent.CanceledException e) { e.printStackTrace(); } - */ - - listener.onIncomingConnection(finalDevice, finalConnection); + //listener.onIncomingConnection(finalDevice, finalConnection); } }; mainHandler.post(myRunnable); - /**/ - /* - try { - Intent dataIntent = new Intent(); - dataIntent.putExtra(EXTRA_DEVICE, this); - dataIntent.putExtra(EXTRA_CONNECTION, connection); - pendingIntent.send(RCClient.getInstance().context, 0, dataIntent); - } - catch (PendingIntent.CanceledException e) { - e.printStackTrace(); - } - */ } public void onSipUAMessageArrived(SipEvent event) @@ -399,23 +394,22 @@ public void onSipUAMessageArrived(SipEvent event) final String finalContent = new String(event.content); final HashMap finalParameters = new HashMap(parameters); - final RCDevice finalDevice = new RCDevice(this); - // Important: need to fire the event in UI context cause currently we 're in JAIN SIP thread Handler mainHandler = new Handler(RCClient.getInstance().context.getMainLooper()); Runnable myRunnable = new Runnable() { @Override public void run() { - /* // bring the App to front try { - pendingIntent.send(); + Intent dataIntent = new Intent(); + dataIntent.setAction("ACTION_INCOMING_MESSAGE"); + dataIntent.putExtra("MESSAGE_PARMS", finalParameters); + dataIntent.putExtra("MESSAGE", finalContent); + pendingIntent.send(RCClient.getInstance().context, 0, dataIntent); } catch (PendingIntent.CanceledException e) { e.printStackTrace(); } - */ - - listener.onIncomingMessage(finalDevice, finalContent, finalParameters); + //listener.onIncomingMessage(finalDevice, finalContent, finalParameters); } }; mainHandler.post(myRunnable); @@ -437,7 +431,8 @@ private boolean haveConnectivity() } } - // Parcelable stuff: + // Parcelable stuff (not needed for now -let's keep around in case we use it at some point): + /* @Override public int describeContents() { return 0; @@ -467,5 +462,6 @@ private RCDevice(Parcel in) { in.readBooleanArray(one); incomingSoundEnabled = one[0]; } + */ } diff --git a/sipua/src/main/java/org/mobicents/restcomm/android/sipua/impl/SoundManager.java b/sipua/src/main/java/org/mobicents/restcomm/android/sipua/impl/SoundManager.java index bf5ccb7d..b02284b5 100644 --- a/sipua/src/main/java/org/mobicents/restcomm/android/sipua/impl/SoundManager.java +++ b/sipua/src/main/java/org/mobicents/restcomm/android/sipua/impl/SoundManager.java @@ -3,6 +3,8 @@ import java.net.InetAddress; import java.net.SocketException; import java.net.UnknownHostException; +import java.util.Timer; +import java.util.TimerTask; import android.content.Context; import android.media.AudioManager; @@ -10,6 +12,7 @@ import android.net.rtp.AudioGroup; import android.net.rtp.AudioStream; import android.net.rtp.RtpStream; +import android.os.Handler; import android.util.Log; public class SoundManager implements AudioManager.OnAudioFocusChangeListener { @@ -18,6 +21,9 @@ public class SoundManager implements AudioManager.OnAudioFocusChangeListener { AudioStream audioStream; AudioGroup audioGroup; InetAddress localAddress; + //Timer timer; + //TimerTask timerTask; + private static final String TAG = "SoundManager"; public SoundManager(Context appContext, String ip){ @@ -39,6 +45,9 @@ public int setupAudioStream() { audioStream = new AudioStream(localAddress); audioStream.setCodec(AudioCodec.PCMU); audioStream.setMode(RtpStream.MODE_NORMAL); + + //AudioCodec codecs[] = AudioCodec.getCodecs(); + //Log.i(TAG, "Test"); } catch (SocketException e) { e.printStackTrace(); @@ -56,27 +65,28 @@ public void startStreaming(int remoteRtpPort, String remoteIp) { // Request audio focus for playback int result = audio.requestAudioFocus(this, AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN); + if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { + //////// DEBUG + if (audio.isBluetoothA2dpOn()) { + // Adjust output for Bluetooth. + Log.i(TAG, "Using Bluetooth"); + } else if (audio.isSpeakerphoneOn()) { + // Adjust output for Speakerphone. + Log.i(TAG, "Using Speaker"); + } else if (audio.isMicrophoneMute()) { + // Adjust output for headsets + Log.i(TAG, "Using Microphone is mute"); + } else { + // If audio plays and noone can hear it, is it still playing? + Log.i(TAG, "Using None ??"); + audio.setSpeakerphoneOn(true); + //audio.setStreamVolume(AudioManager.STREAM_VOICE_CALL, 1, 0); + } + //audio.setSpeakerphoneOn(false); - //////// DEBUG - if (audio.isBluetoothA2dpOn()) { - // Adjust output for Bluetooth. - Log.i(TAG, "Using Bluetooth"); - } else if (audio.isSpeakerphoneOn()) { - // Adjust output for Speakerphone. - Log.i(TAG, "Using Speaker"); - } else if (audio.isMicrophoneMute()) { - // Adjust output for headsets - Log.i(TAG, "Using Microphone is mute"); - } else { - // If audio plays and noone can hear it, is it still playing? - Log.i(TAG, "Using None ??"); - //audio.setSpeakerphoneOn(true); - } - //audio.setSpeakerphoneOn(false); + Log.i(TAG, "Vol/max: " + audio.getStreamVolume(audio.STREAM_VOICE_CALL) + "/" + audio.getStreamMaxVolume(audio.STREAM_VOICE_CALL)); - Log.i(TAG, "Vol/max: " + audio.getStreamVolume(audio.STREAM_VOICE_CALL) + "/" + audio.getStreamMaxVolume(audio.STREAM_VOICE_CALL)); - if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { try { audioStream.associate( InetAddress.getByName(remoteIp), @@ -99,34 +109,36 @@ public void startStreaming(int remoteRtpPort, String remoteIp) { Log.e(TAG, "Cannot receive audio focus; media stream not setup"); } - } public void stopStreaming() { - AudioStream streams[] = audioGroup.getStreams(); - for (int i = 0; i < streams.length; i++) { - Log.i(TAG, "-------- Stream: " + i); - AudioCodec codec = streams[i].getCodec(); - Log.i(TAG, "fmtp: " + codec.fmtp + ", rtpmap: " + codec.rtpmap + ", type: " + codec.type); - } - - System.out.println("Releasing Audio: "); - try { - audioStream.join(null); - } catch (IllegalStateException e) { - e.printStackTrace(); - } - - audioGroup.clear(); - if (audioStream.isBusy()) { - Log.i(TAG, "AudioStream is busy"); - } - //audioStream.release(); - audioStream = null; - audio.setMode(AudioManager.MODE_NORMAL); - - // Abandon audio focus when playback complete - audio.abandonAudioFocus(this); + // workaround: android RTP facilities seem to induce around 500ms delay in the incoming media stream. + // Let's delay the media tear-down to avoid media truncation for now + final SoundManager finalSoundManager = this; + Handler mainHandler = new Handler(appContext.getMainLooper()); + Runnable myRunnable = new Runnable() { + @Override + public void run() { + Log.i(TAG, "Releasing Audio: "); + try { + audioStream.join(null); + } catch (IllegalStateException e) { + e.printStackTrace(); + } + + audioGroup.clear(); + if (audioStream.isBusy()) { + Log.i(TAG, "AudioStream is busy"); + } + //audioStream.release(); + audioStream = null; + audio.setMode(AudioManager.MODE_NORMAL); + + // Abandon audio focus when playback complete + audio.abandonAudioFocus(finalSoundManager); + } + }; + mainHandler.postDelayed(myRunnable, 500); } public void muteAudio(boolean muted) diff --git a/sipua/src/main/java/org/mobicents/restcomm/android/sipua/impl/sipmessages/Invite.java b/sipua/src/main/java/org/mobicents/restcomm/android/sipua/impl/sipmessages/Invite.java index ad9b5409..55827751 100644 --- a/sipua/src/main/java/org/mobicents/restcomm/android/sipua/impl/sipmessages/Invite.java +++ b/sipua/src/main/java/org/mobicents/restcomm/android/sipua/impl/sipmessages/Invite.java @@ -93,13 +93,19 @@ public Request MakeRequest(SipManager sipManager,String to, int port){ "my header value"); callRequest.addHeader(extensionHeader); - String sdpData= "v=0\r\n" - + "o=4855 13760799956958020 13760799956958020" - + " IN IP4 " + sipManager.getSipProfile().getLocalIp() +"\r\n" + "s=mysession session\r\n" - + "p=+46 8 52018010\r\n" + "c=IN IP4 " + sipManager.getSipProfile().getLocalIp()+"\r\n" - + "t=0 0\r\n" + "m=audio "+port+" RTP/AVP 0 4 18\r\n" - + "a=rtpmap:0 PCMU/8000\r\n" + "a=rtpmap:4 G723/8000\r\n" - + "a=rtpmap:18 G729A/8000\r\n" + "a=ptime:20\r\n"; + String sdpData= "v=0\r\n" + + "o=- 13760799956958020 13760799956958020" + " IN IP4 " + sipManager.getSipProfile().getLocalIp() +"\r\n" + + //"s=mysession session\r\n" + + "s=-\r\n" + + //"p=+46 8 52018010\r\n" + + "c=IN IP4 " + sipManager.getSipProfile().getLocalIp()+"\r\n" + + "t=0 0\r\n" + + "m=audio " + port + " RTP/AVP 0\r\n" + + //"m=audio " + port + " RTP/AVP 0 4 18\r\n" + + "a=rtpmap:0 PCMU/8000\r\n" + + //"a=rtpmap:4 G723/8000\r\n" + + //"a=rtpmap:18 G729A/8000\r\n" + + "a=ptime:20\r\n"; byte[] contents = sdpData.getBytes(); callRequest.setContent(contents, contentTypeHeader);