How to record phone calls in Android - java

I am developing an application which can record calls in Android. I have read a lot of topics where the call recording problem was discussed. And i know that not all Android phones can record calls. But i am wondering how can record calls the most popular applications on Play Market, such as https://play.google.com/store/apps/details?id=com.appstar.callrecorder or https://play.google.com/store/apps/details?id=polis.app.callrecorder. I think that thy are using not on MediaRecorder class to do this job, but also something else. Because i have developed my own application, but i can record only my voice. But these two applications are recording both my voice and the voice of a man to whom i am calling. How they are doing this? I know that we can't get an access to device speaker to record sound from it. Could you give me some ideas of how to record voice calls? Here is my code that i am using in my application:
public class CallRecorderService extends Service {
private MediaRecorder mRecorder;
private boolean isRecording = false;
private PhoneStateListener phoneStateListener = new PhoneStateListener() {
#Override
public void onCallStateChanged(int state, String incomingNumber) {
super.onCallStateChanged(state, incomingNumber);
switch (state) {
case TelephonyManager.CALL_STATE_IDLE:
stopRecording();
break;
case TelephonyManager.CALL_STATE_OFFHOOK:
startRecording(incomingNumber);
break;
case TelephonyManager.CALL_STATE_RINGING:
break;
default:
break;
}
}
};
#Override
public void onCreate() {
TelephonyManager telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
super.onCreate();
}
#Override
public void onDestroy() {
super.onDestroy();
}
#Override
public IBinder onBind(Intent intent) {
return null;
}
private void startRecording(String number) {
try {
String savePath = Environment.getExternalStorageDirectory().getAbsolutePath();
savePath += "/Recorded";
File file = new File(savePath);
if (!file.exists()) {
file.mkdir();
}
savePath += "/record_" + System.currentTimeMillis() + ".amr";
mRecorder = new MediaRecorder();
SharedPreferences sPrefs = getSharedPreferences(Constants.PREFERENCES_NAME, Context.MODE_PRIVATE);
int inputSource = sPrefs.getInt(Constants.SOURCE_INPUT, Constants.SOURCE_VOICE_CALL);
int outputSource = sPrefs.getInt(Constants.SOURCE_OUTPUT, Constants.OUTPUT_MPEG4);
switch (inputSource) {
case Constants.SOURCE_MIC:
increaseSpeakerVolume();
break;
}
mRecorder.setAudioSource(inputSource);
mRecorder.setOutputFormat(outputSource);
mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
mRecorder.setOutputFile(savePath);
mRecorder.prepare();
mRecorder.start();
isRecording = true;
} catch (Exception ex) {
ex.printStackTrace();
}
}
private void stopRecording(){
if (isRecording) {
isRecording = false;
mRecorder.stop();
mRecorder.release();
}
}
private void increaseSpeakerVolume(){
AudioManager audio = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
audio.adjustStreamVolume(AudioManager.STREAM_VOICE_CALL,
AudioManager.ADJUST_RAISE,
AudioManager.FLAG_SHOW_UI);
}
}

I think they do use MediaRecorder. Perhaps main problem with it is that, it is tricky to write the data from it into something else but file on local store. In order to work it around, you could create a pipe. Then use its file descriptor as an input fd for MediaRecorder. And the pipe output then could be controlled by some thread that will read the audio packages (in one of format, aac or.. wav even) and then do whatever you want with the data.
Here is some code. Note, this is a proto, may not even compile, just to give you an idea.
/* 2M buffer should be enough. */
private final static int SOCKET_BUF_SIZE = 2 * 1024 * 1000;
private void openSockets() throws IOException {
receiver = new LocalSocket();
receiver.connect(new LocalSocketAddress("com.companyname.media-" + socketId));
receiver.setReceiveBufferSize(SOCKET_BUF_SIZE);
sender = lss.accept();
sender.setSendBufferSize(SOCKET_BUF_SIZE);
}
private void closeSockets() {
try {
if (sender != null) {
sender.close();
sender = null;
}
if (receiver != null) {
receiver.close();
receiver = null;
}
} catch (Exception ignore) {
}
}
public void prepare() throws IllegalStateException, IOException {
int samplingRate;
openSockets();
if (mode == MODE_TESTING) {
samplingRate = requestedSamplingRate;
} else {
samplingRate = actualSamplingRate;
}
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
try {
Field name = MediaRecorder.OutputFormat.class.getField("AAC_ADTS");
if (BuildConfig.DEBUG)
Log.d(StreamingApp.TAG, "AAC ADTS seems to be supported: AAC_ADTS=" + name.getInt(null));
mediaRecorder.setOutputFormat(name.getInt(null));
} catch (Exception e) {
throw new IOException("AAC is not supported.");
}
try {
mediaRecorder.setMaxDuration(-1);
mediaRecorder.setMaxFileSize(Integer.MAX_VALUE);
} catch (RuntimeException e) {
Log.e(StreamingApp.TAG, "setMaxDuration or setMaxFileSize failed!");
}
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
mediaRecorder.setAudioChannels(1);
mediaRecorder.setAudioSamplingRate(samplingRate);
mediaRecorder.setOutputFile(sender.getFileDescriptor());
startListenThread(receiver.getInputStream());
mediaRecorder.start();
}

Related

Can't download from Google Play Store when VPN connection is active

I`m trying to implement "Change DNS programmatically logic" in my app. Everything working well but when i try to download/update app thru Google play it's doesn't start only progressing without stop. When i stop my service everything working well and i can download/update apps without any problem.
This is my Service:
public class VPNService extends VpnService {
private VpnService.Builder builder = new VpnService.Builder();
private ParcelFileDescriptor fileDescriptor;
private Thread mThread;
private boolean shouldRun = true;
private DatagramChannel tunnel;
public static final String ACTION_CONNECT = VPNService.class.getName() + ".START";
public static final String ACTION_DISCONNECT = VPNService.class.getName() + ".STOP";
#Override
public void onDestroy() {
if (mThread != null) {
mThread.interrupt();
}
super.onDestroy();
}
#Override
public void onCreate() {
super.onCreate();
startForeground(999, new Notification());
}
private void setTunnel(DatagramChannel tunnel) {
this.tunnel = tunnel;
}
private void setFileDescriptor(ParcelFileDescriptor fileDescriptor) {
this.fileDescriptor = fileDescriptor;
}
#Override
public int onStartCommand(final Intent intent, int flags, int startId) {
if (intent != null && ACTION_DISCONNECT.equals(intent.getAction())) {
onDestroy();
return Service.START_NOT_STICKY;
}
mThread = new Thread(() -> {
try {
setFileDescriptor(builder.setSession(VPNService.this.getText(R.string.app_name).toString()).
addAddress("192.168.0.1", 24).addDnsServer("94.155.240.3").addDnsServer("212.50.87.211").establish());
setTunnel(DatagramChannel.open());
tunnel.connect(new InetSocketAddress("127.0.0.1", 8087));
protect(tunnel.socket());
while (shouldRun)
Thread.sleep(100L);
} catch (Exception exception) {
exception.printStackTrace();
} finally {
if (fileDescriptor != null) {
try {
fileDescriptor.close();
setFileDescriptor(null);
} catch (IOException e) {
e.printStackTrace();
}
}
}
});
mThread.start();
return Service.START_STICKY;
}
public static void startVService(Context context) {
VPNService.prepare(context);
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
ContextCompat.startForegroundService(context,new Intent(context, VPNService.class).setAction(ACTION_CONNECT));
else
context.startService(new Intent(context, VPNService.class).setAction(ACTION_CONNECT));
}catch (RuntimeException e){
Log.d("VPNService","Not allowed to start service Intent",e);
}
}
}
You may have to exclude playstore from the vpn.
https://www.reddit.com/r/ProtonVPN/comments/bzh6ir/apps_not_downloadingupdating_via_google_play/
vpnBuilder.addDisallowedApplication(packageName); // where package name is com.android.vending (Play store)

how to recognize only loud voice (words) with pocketsphinx on android

I am building android app which have to listen all the time (constantly) a voice and catch a key word like eg help. I am using now MediaRecorder to get an amplitude, then if is loud (eg 20000), I call pocketsphinx speechrecognizer. The problem is that when speechrecognizer caught (or not) the key word I can't jump back to MediaRecorder, the app is crashed. Of course my app must work in the background (24 h/day) so my implementation is in Service, so my MediaRecorder is in separate thread.
I know that pocketsphinx can check also the amplitude (a scream), but how to make it? And is pocketsphinx (get amplitude) better solution to trigger the speech recognizer? Below my class, I would be very appreciate for any help.
#Override
public IBinder onBind(Intent intent) {
return null;
}
private final class ServiceHandler extends Handler{
public ServiceHandler(Looper looper){
super(looper);
}
#Override
public void handleMessage(Message msg){
outputFile = Environment.getExternalStorageDirectory().getAbsolutePath() + "/record.3gp";
getVoiceRecord();
}
}
#Override
public void onCreate() {
thread = new HandlerThread("ServiceStartArguments",
Process.THREAD_PRIORITY_BACKGROUND);
thread.start();
// Get the HandlerThread's Looper and use it for our Handler
serviceLooper = thread.getLooper();
serviceHandler = new ServiceHandler(serviceLooper);
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
//TODO do something useful
//change to START_STICKY
Log.d("tag", "on start command");
Message msg = serviceHandler.obtainMessage();
msg.arg1 = startId;
serviceHandler.sendMessage(msg);
return Service.START_NOT_STICKY;
}
private void getVoiceRecord() {
startRecorder();
start = System.currentTimeMillis();
Log.d("tag", "Time started at " + start);
while (true){
if(recorder!=null){
amplitude = recorder.getMaxAmplitude();
if(amplitude>20000){
Toast.makeText(getApplicationContext(), "Scream detected",
Toast.LENGTH_LONG).show();
Log.d("tag", "Scream detected " + 20 * Math.log10(amplitude) + " amplitude: " + amplitude);
stopRecorder();
Log.d("tag", "Finish recording");
getSpeech();
}
finish = System.currentTimeMillis();
if(finish-start>50000){
//loop = false;
stopRecorder();
Log.d("tag", "Finish recording");
if(recorder==null){
recorder.reset();
startRecorder();
start = System.currentTimeMillis();
}
}
}
}//end of while loop
}
private void getSpeech() {
try {
Assets assets = new Assets(ScreamService.this);
File assetDir = assets.syncAssets();
setupRecognizer(assetDir);
} catch (IOException e) {
//return e;
}
reset();
}
private void stopRecorder() {
try{
recorder.stop();
recorder.reset();
recorder.release();
recorder = null;
Log.d("tag", "Stop recording");
}catch (IllegalStateException e) {
e.printStackTrace();
Log.d("tag", "Media Recorder did not stop " + e);
try {
Thread.sleep(2000);
recorder.stop();
recorder.release();
recorder = null;
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}catch (RuntimeException e) {
e.printStackTrace();
Log.d("tag", "Media Recorder did not stop " + e);
}
}
private void startRecorder() {
Log.d("tag", "Start recording... ");
try {
recorder = new MediaRecorder();
recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
recorder.setOutputFile(outputFile);
recorder.prepare();
recorder.start();
} catch (IOException e) {
e.printStackTrace();
Log.d("tag", "Media Recorder did not start IOExeption " + e);
} catch (IllegalStateException e) {
e.printStackTrace();
Log.d("tag", "Media Recorder did not start Ilegal State Trace" + e);
}
}
#Override
public void onDestroy() {
super.onDestroy();
recognizer.cancel();
recognizer.shutdown();
Toast.makeText(this, "Scream Service & recognizer Stopped.", Toast.LENGTH_SHORT).show();
}
#Override
public void onPartialResult(Hypothesis hypothesis) {
}
/**
* This callback is called when we stop the recognizer.
*/
#Override
public void onResult(Hypothesis hypothesis) {
if (hypothesis != null) {
String text = hypothesis.getHypstr();
//makeText(getApplicationContext(), text, Toast.LENGTH_SHORT).show();
Log.d("tag", "onResult " + text);
if(text.equals("help") || text.equals("help me")) {
recognizer.stop();
recognizer.cancel();
this.startService(new Intent(this, SendMessage.class));
getVoiceRecord();
}
}else {
Log.d("tag", "onResult is null");
}
}
#Override
public void onBeginningOfSpeech() {
//Log.d("tag", "onBeginningOfSpeech ");
}
#Override
public void onEndOfSpeech() {
counter++;
if(counter>5){
//recognizer.stop();
recognizer.cancel();
counter=0;
getVoiceRecord();
Log.d("tag", "Speech recognizer is killed");
}else {
//Log.d("tag", "onEndOfSpeech ");
reset();
}
}
private void reset() {
recognizer.stop();
recognizer.startListening("menu");
}
private void setupRecognizer(File assetsDir) throws IOException {
Log.d("tag", "default setup");
recognizer = defaultSetup()
.setAcousticModel(new File(assetsDir, "en-us-ptm"))
.setDictionary(new File(assetsDir, "cmudict-en-us.dict"))
// To disable logging of raw audio comment out this call (takes a lot of space on the device)
.setRawLogDir(assetsDir)
// Threshold to tune for keyphrase to balance between false alarms and misses
.setKeywordThreshold(1e-45f)
// Use context-independent phonetic search, context-dependent is too slow for mobile
.setBoolean("-allphone_ci", true)
.getRecognizer();
recognizer.addListener(this);
// Create grammar-based search for selection between demos
File menuGrammar = new File(assetsDir, "menu.gram");
recognizer.addGrammarSearch("menu", menuGrammar);
}
#Override
public void onError(Exception error) {
Log.d("tag", "error "+error.getMessage());
}
#Override
public void onTimeout() {
Log.d("tag", "onTimeout");
}
You can modify pocketsphinx sources to compute amplitude of recorded audio data before you pass it into recognizer. In SpeechRecognizer.java RecognizerThread class:
.........
while (!interrupted()
&& ((timeoutSamples == NO_TIMEOUT) || (remainingSamples > 0))) {
int nread = recorder.read(buffer, 0, buffer.length);
if (-1 == nread) {
throw new RuntimeException("error reading audio buffer");
} else if (nread > 0) {
// int max = 0;
// for (int i = 0; i < nread; i++) {
// max = Math.max(max, Math.abs(buffer[i]));
// }
// Log.e("!!!!!!!!", "Level is: " + max);
// You can decide to skip buffer here
decoder.processRaw(buffer, nread, false, false);
......

Service Socket Disconnect on Background->Foreground switch

I am writing an IRC Client. The socket connection to the IRC Server is handled via a service. I have managed to stabilize all the UI elements of the Activities in question during the orientation change, but somehow the socket that is maintained by the service is being closed during the change.
Here is what I believe to be the relevant code. Please let me know if you need to see more.
//This is the Service in question
public class ConnectionService extends Service{
private BlockingQueue<String> MessageQueue;
public final IBinder myBind = new ConnectionBinder();
public class ConnectionBinder extends Binder {
ConnectionService getService() {
return ConnectionService.this;
}
}
private Socket socket;
private BufferedWriter writer;
private BufferedReader reader;
private IRCServer server;
private WifiManager.WifiLock wLock;
private Thread readThread = new Thread(new Runnable() {
#Override
public void run() {
try {
String line;
while ((line = reader.readLine( )) != null) {
if (line.toUpperCase().startsWith("PING ")) {
SendMessage("PONG " + line.substring(5));
}
else
queueMessage(line);
}
}
catch (Exception e) {}
}
});
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
if(MessageQueue == null)
MessageQueue = new LinkedBlockingQueue<String>();
return Service.START_STICKY;
}
#Override
public IBinder onBind(Intent arg0) {
return myBind;
}
#Override
public boolean stopService(Intent name) {
try {
socket.close();
wLock.release();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return super.stopService(name);
}
#Override
public void onDestroy()
{//I put this here so I had a breakpoint in place to make sure this wasn't firing instead of stopService
try {
socket.close();
wLock.release();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
super.onDestroy();
}
public void SendMessage(String message)
{
try {
writer.write(message + "\r\n");
writer.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
public String readLine()
{
try {
if(!isConnected())
return null;
else
return MessageQueue.take();
} catch (InterruptedException e) {
return "";
}
}
public boolean ConnectToServer(IRCServer newServer)
{
try {
//create a new message queue (connecting to a new server)
MessageQueue = new LinkedBlockingQueue<String>();
//lock the wifi
WifiManager wifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
wLock = wifiManager.createWifiLock(WifiManager.WIFI_MODE_FULL, "LockTag");
wLock.acquire();
server = newServer;
//connect to server
socket = new Socket();
socket.setKeepAlive(true);
socket.setSoTimeout(60000);
socket.connect(new InetSocketAddress(server.NAME, Integer.parseInt(server.PORT)), 10000);
writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//run basic login scripts.
if(server.PASS != "")
SendMessage("PASS " + server.PASS);
//write nickname
SendMessage("NICK " + server.NICK);
//write username login
SendMessage("USER " + server.NICK + " 0 * :Fluffy IRC");
String line;
while ((line = reader.readLine( )) != null) {
if (line.indexOf("004") >= 0) {
// We are now logged in.
break;
}
else if (line.indexOf("433") >= 0) {
//change to alt Nick
if(!server.NICK.equals(server.ALT_NICK) && !server.ALT_NICK.equals(""))
{
server.NICK = server.ALT_NICK;
SendMessage("NICK " + server.NICK);
}
else
{
queueMessage("Nickname already in use");
socket.close();
return false;
}
}
else if (line.toUpperCase().startsWith("PING ")) {
SendMessage("PONG " + line.substring(5));
}
else
{
queueMessage(line);
}
}
//start the reader thread AFTER the primary login!!!
CheckStartReader();
if(server.START_CHANNEL == null || server.START_CHANNEL == "")
{
server.WriteCommand("/join " + server.START_CHANNEL);
}
//we're done here, go home everyone
} catch (NumberFormatException e) {
return false;
} catch (IOException e) {
return false;
}
return true;
}
private void queueMessage(String line) {
try {
MessageQueue.put(line);
} catch (InterruptedException e) {
}
}
public boolean isConnected()
{
return socket.isConnected();
}
public void CheckStartReader()
{
if(this.isConnected() && !readThread.isAlive())
readThread.start();
}
}
//Here are the relevant portions of the hosting Activity that connects to the service
//NOTE: THE FOLLOWING CODE IS PART OF THE ACTIVITY, NOT THE SERVICE
private ConnectionService conn;
private ServiceConnection mConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName name, IBinder service) {
conn = ((ConnectionService.ConnectionBinder)service).getService();
Toast.makeText(main_tab_page.this, "Connected", Toast.LENGTH_SHORT)
.show();
synchronized (_serviceConnWait) {
_serviceConnWait.notify();
}
}
#Override
public void onServiceDisconnected(ComponentName name) {
conn = null;
}
};
#Override
protected void onSaveInstanceState(Bundle state){
super.onSaveInstanceState(state);
state.putParcelable("Server", server);
state.putString("Window", CurrentTabWindow.GetName());
unbindService(mConnection);
}
#Override
protected void onDestroy()
{
super.onDestroy();
if(this.isFinishing())
stopService(new Intent(this, ConnectionService.class));
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_tab_page);
localTabHost = (TabHost)findViewById(R.id.tabHostMain);
localTabHost.setup();
localTabHost.setOnTabChangedListener(new tabChange());
_serviceConnWait = new Object();
if(savedInstanceState == null)
{//initial startup, coming from Intent to start
//get server definition
server = (IRCServer)this.getIntent().getParcelableExtra(IRC_WINDOW);
server.addObserver(this);
AddTabView(server);
startService(new Intent(this, ConnectionService.class));
}
else
{
server = (IRCServer)savedInstanceState.getParcelable("Server");
String windowName = savedInstanceState.getString("Window");
//Add Needed Tabs
//Server
if(!(windowName.equals(server.GetName())))
AddTabView(server);
//channels
for(IRCChannel c : server.GetAllChannels())
if(!(windowName.equals(c.GetName())))
AddTabView(c);
//reset each view's text (handled by tabChange)
if(windowName.equals(server.GetName()))
SetCurrentTab(server.NAME);
else
SetCurrentTab(windowName);
ResetMainView(CurrentTabWindow.GetWindowTextSpan());
//Rebind to service
BindToService(new Intent(this, ConnectionService.class));
}
}
#Override
protected void onStart()
{
super.onStart();
final Intent ServiceIntent = new Intent(this, ConnectionService.class);
//check start connection service
final Thread serverConnect = new Thread(new Runnable() {
#Override
public void run() {
if(!BindToService(ServiceIntent))
return;
server.conn = conn;
conn.ConnectToServer(server);
server.StartReader();
if(server.START_CHANNEL != null && !server.START_CHANNEL.equals(""))
{
IRCChannel chan = server.FindChannel(server.START_CHANNEL);
if(chan != null)
{
AddTabView(chan);
}
else
{
server.JoinChannel(server.START_CHANNEL);
chan = server.FindChannel(server.START_CHANNEL);
AddTabView(chan);
}
}
}
});
serverConnect.start();
}
private boolean BindToService(Intent ServiceIntent)
{
int tryCount = 0;
bindService(ServiceIntent, mConnection, Context.BIND_AUTO_CREATE);
while(conn == null && tryCount < 10)
{
tryCount++;
try {
synchronized (_serviceConnWait) {
_serviceConnWait.wait(1500);
}
}
catch (InterruptedException e) {
//do nothing
}
}
return conn != null;
}
Im not entirely certain what I am doing wrong there. Obviously there's something I'm missing, haven't found yet, or haven't even thought to check. What happens though is that after the orientation change my Send command gives me this message and nothing happens:
06-04 22:02:27.637: W/System.err(1024): java.net.SocketException: Socket closed
06-04 22:02:27.982: W/System.err(1024): at com.fluffyirc.ConnectionService.SendMessage(ConnectionService.java:90)
I have no idea when the socket is getting closed, or why.
Update
I have changed the code so that rather than binding to the service and using that to start it, instead I call startService and stopService at appropriate points as well as binding to it, on the thought that the service was being destroyed when the binding was lost. This is working exactly like it was before I changed it. The socket still closes on an orientation change, and I have no idea why.
Update :- Code and description
I added the code changes recently made for Start/Stop service and START_STICKY. I also recently read a very good article explaining how the orientation change process flow works and why its NOT a bad idea to add the android:configChanges="orientation|screenSize" line to your manifest. So this fixed the orientation issue, but its still doing the same thing if I put the activity into background mode, and then bring it back to the foreground. That still follows the same Save/Destroy/Create process that the orientation does without that manifest line...and it still closes my socket, and I still don't know why.
I do know that it doesn't close the socket until the re-create process...I know this because the message queue will display messages that were received while the app was in the background, but once I bring it back forward it closes the socket and nothing else can be sent or received.
'Socket closed' means that you closed the socket and then continued to use it. It isn't a 'disconnect'.
You need to put something into that catch block. Never just ignore an exception. You might get a surprise when you see what the exception actually was.
NB Socket.isConnected() doesn't tell you anything about the state of the connection: only whether you have ever connected the Socket. You have, so it returns true.

Android MediaPlayer setNextMediaPlayer() alternative

I published an app recently, and users are reporting crashes because my program included the method setNextMediaPlayer(). I now realize that this only works for API 16+, and my app supports API 8+. I was wondering if there is an alternate way of achieving the same effect.
My app has some text to speech, and what I was doing was creating an ArrayList of MediaPlayers that each had a short sound file, and then playing them one after an other. If I remove this method from the code, the audio becomes too choppy to understand.
I was thinking about using the SoundPool class, but there is no OnCompleteListener, so I'm not sure how I would do it.
So basically my question is: Is there a way to transition seamlessly between audio files without using the setNextMediaPlayer() method?
Thanks very much for your time!
EDIT
I added this code that I found
private class CompatMediaPlayer extends MediaPlayer implements OnCompletionListener {
private boolean mCompatMode = true;
private MediaPlayer mNextPlayer;
private OnCompletionListener mCompletion;
public CompatMediaPlayer() {
try {
MediaPlayer.class.getMethod("setNextMediaPlayer", MediaPlayer.class);
mCompatMode = false;
} catch (NoSuchMethodException e) {
mCompatMode = true;
super.setOnCompletionListener(this);
}
}
public void setNextMediaPlayer(MediaPlayer next) {
if (mCompatMode) {
mNextPlayer = next;
} else {
super.setNextMediaPlayer(next);
}
}
#Override
public void setOnCompletionListener(OnCompletionListener listener) {
if (mCompatMode) {
mCompletion = listener;
} else {
super.setOnCompletionListener(listener);
}
}
#Override
public void onCompletion(MediaPlayer mp) {
if (mNextPlayer != null) {
// as it turns out, starting a new MediaPlayer on the completion
// of a previous player ends up slightly overlapping the two
// playbacks, so slightly delaying the start of the next player
// gives a better user experience
SystemClock.sleep(50);
mNextPlayer.start();
}
mCompletion.onCompletion(this);
}
}
But now how do I add the audio files? I tried this:
// assigns a file to each media player
mediaplayers = new ArrayList<CompatMediaPlayer>();
for (int i = 0; i < files.size(); i++) {
mediaplayers.add((CompatMediaPlayer) CompatMediaPlayer.create(this, files.get(i)));
}
but am getting a class cast exception because MediaPlayer cannot be casted to CompatMediaPlayer.
Create Compat player that will work with onCompletionListener to start the next player like:
public void onCompletion(MediaPlayer mp) {
if (mCompatMode && mNextPlayer != null) {
mNextPlayer.prepare();
mNextPlayer.start();
}
}
Somewhere in your constructor check if there is method (or check SDK version) named "setNextMediaPlayer"
mCompatMode = Build.VERSION.SDK_INT < 16;
Define method like this one:
public void setNextMediaPlayer(MediaPlayer next) {
if (mCompatMode) {
mNextPlayer = next;
} else {
super.setNextMediaPlayer(next);
}
}
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.stop();
mediaPlayer.release();
mediaPlayer = null;
}
mediaPlayer = new MediaPlayer();
try {
AssetFileDescriptor descriptor = context.getAssets().openFd("play1.mp3");
mediaPlayer.setDataSource(descriptor.getFileDescriptor(), descriptor.getStartOffset(), descriptor.getLength());
descriptor.close();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.prepare();
} catch (IOException e) {
e.printStackTrace();
}
mediaPlayer.start();
MediaPlayer md = new MediaPlayer();
try {
AssetFileDescriptor descriptor = context.getAssets().openFd("play2.mp3");
md.setDataSource(descriptor.getFileDescriptor(), descriptor.getStartOffset(), descriptor.getLength());
descriptor.close();
md.setAudioStreamType(AudioManager.STREAM_MUSIC);
md.prepare();
} catch (IOException e) {
e.printStackTrace();
}
mediaPlayer.setNextMediaPlayer(md);
MediaPlayer md1 = new MediaPlayer();
try {
AssetFileDescriptor descriptor = context.getAssets().openFd("play3.mp3");
md1.setDataSource(descriptor.getFileDescriptor(), descriptor.getStartOffset(), descriptor.getLength());
descriptor.close();
md1.setAudioStreamType(AudioManager.STREAM_MUSIC);
md1.prepare();
} catch (IOException e) {
e.printStackTrace();
} md.setNextMediaPlayer(md1);

Connection from Activity to Service takes too long

Im currently developing a Music Player and due to the fact that each time the orientation changes on the Phone and the Activity is re-created, I wanted the music to be played by a service. This way, the user is able to leave the activity without the music stopping..
Now.. I have this weird issue I been unable to solve... Each time I created the Activity and Inflate the GUI, the service is started. But the Service always gets Bounded after the Activity has send the data... So the music never starts... I know this happens because if I add a Button to resend the data, the Music starts playing... Here is my code for the activity:
public class Player extends Activity{
private Cursor audioCursor;
public static int position=0;
private int count;
private boolean pause = false,
play= false,
stop= false,
next= false,
back= false,
playerActive= true,
dataChanged= false,
finished= false,
playing= true;
private String action;
Messenger mService = null;
boolean mIsBound;
final Messenger mMessenger = new Messenger(new IncomingHandler());
private ServiceConnection mConnection=null;
static final int MSG_SET_BOOLEAN_VALUE = 5;
static final int MSG_SET_STRING_VALUE = 4;
static final int MSG_SET_INT_VALUE = 3;
#Override
protected void onCreate(Bundle savedInstanceState) {
requestWindowFeature(Window.FEATURE_NO_TITLE);
super.onCreate(savedInstanceState);
setContentView(R.layout.player);
Bundle extras = getIntent().getExtras();
action=extras.getString("action");
if(!(Background.isRunning()))
startService(new Intent(Player.this, Background.class));
doBindService();
if(action.equals("play")){
position=extras.getInt("position");
String[] proj = {
MediaStore.Audio.Media.ARTIST,
MediaStore.Audio.Media.TITLE,
MediaStore.Audio.Media.ALBUM,
MediaStore.Audio.Media.DURATION,
MediaStore.Audio.Media.IS_MUSIC,
MediaStore.Audio.Media.ALBUM_ID};
audioCursor = getContentResolver().query(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, proj,
MediaStore.Audio.Media.IS_MUSIC, null,
MediaStore.Audio.Media.TITLE + " ASC");
startManagingCursor(audioCursor);
count = audioCursor.getCount();
inflatePlayer();
/////////////////////THIS IS THE CODE THAT ACTS BEFORE THE SERVICE CONNECTION
sendBoolToService(playerActive, "playerActive");
sendIntToService(position);
sendStringToService(action);
}
}
//THIS CODE MUST BE FASTER, BUT THE CONNECTION TAKES TOO LONG
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
mService = new Messenger(service);
Toast.makeText(getApplicationContext(), "ATTACHED!", Toast.LENGTH_LONG).show();
try {
Message msg = Message.obtain(null, Background.MSG_REGISTER_CLIENT);
msg.replyTo = mMessenger;
mService.send(msg);
} catch (RemoteException e) {
Toast.makeText(getApplicationContext(), "Connection failed!", Toast.LENGTH_LONG).show();
}
}
public void onServiceDisconnected(ComponentName className) {
mService = null;
Toast.makeText(getApplicationContext(), "UNATTACHED!", Toast.LENGTH_LONG).show();
}
};
private void inflatePlayer(){
//LOTS OF CODE FOR THE GUI, NOTHING TO DO WITH THE SERVICE... SO I OMITTED IT
}
#Override
protected void onStop(){
playerActive=false;
try {
doUnbindService();
} catch (Throwable t) {
}
if(!playing)
stopService(new Intent(Player.this, Background.class));
super.onStop();
}
#Override
protected void onDestroy(){
playerActive=false;
audioCursor.close();
try {
doUnbindService();
} catch (Throwable t) {
}
if(!playing)
stopService(new Intent(Player.this, Background.class));
super.onDestroy();
}
class IncomingHandler extends Handler {
#Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_SET_INT_VALUE:
String str = Integer.toString(msg.getData().getInt("int1"));
Toast.makeText(getApplicationContext(), "Int Message: " + str, Toast.LENGTH_LONG).show();
break;
case MSG_SET_STRING_VALUE:
String str1 = msg.getData().getString("str1");
break;
case MSG_SET_BOOLEAN_VALUE:
dataChanged=msg.getData().getBoolean("dataChanged");
finished=msg.getData().getBoolean("finished");
playing=msg.getData().getBoolean("playing");
if(!playing){
if(finished){
finished=false;
finish();
}
}
default:
super.handleMessage(msg);
}
}
}
private void sendIntToService(int intvaluetosend) {
if (mService != null) {
try {
Bundle b = new Bundle();
b.putInt("int1", intvaluetosend);
Message msg = Message.obtain(null, MSG_SET_INT_VALUE);
msg.setData(b);
mService.send(msg);
} catch (RemoteException e) {
}
}
}
private void sendStringToService(String stringtosend) {
if (mService != null) {
try {
Bundle b = new Bundle();
b.putString("str1", stringtosend);
Message msg = Message.obtain(null, MSG_SET_STRING_VALUE);
msg.setData(b);
mService.send(msg);
} catch (RemoteException e) {
}
}
}
private void sendBoolToService(boolean booltosend, String name) {
if (mService != null) {
try {
Bundle b = new Bundle();
b.putBoolean(name, booltosend);
Message msg = Message.obtain(null, MSG_SET_BOOLEAN_VALUE);
msg.setData(b);
mService.send(msg);
} catch (RemoteException e) {
}
}
}
void doBindService() {
bindService(new Intent(this, Background.class), mConnection, Context.BIND_AUTO_CREATE);
mIsBound = true;
Toast.makeText(getApplicationContext(), "BOUND!", Toast.LENGTH_LONG).show();
}
void doUnbindService() {
if (mIsBound) {
if (mService != null) {
try {
Message msg = Message.obtain(null, Background.MSG_UNREGISTER_CLIENT);
msg.replyTo = mMessenger;
mService.send(msg);
} catch (RemoteException e) {
}
}
unbindService(mConnection);
mIsBound = false;
Toast.makeText(getApplicationContext(), "UNBOUND!", Toast.LENGTH_LONG).show();
}
}
}
The Service:
public class Background extends Service {
private NotificationManager nm;
private Cursor audioCursor;
MediaPlayer mp = new MediaPlayer();
private int count;
private boolean pause = false,
play= false,
stop= false,
next= false,
back= false,
playerActive= true,
dataChanged= false,
finished= false,
playing= false;
private int position;
private String action;
ArrayList<Messenger> mClients = new ArrayList<Messenger>();
static final int MSG_REGISTER_CLIENT = 1;
static final int MSG_UNREGISTER_CLIENT = 2;
static final int MSG_SET_INT_VALUE = 3;
static final int MSG_SET_STRING_VALUE = 4;
static final int MSG_SET_BOOLEAN_VALUE = 5;
final Messenger mMessenger = new Messenger(new IncomingHandler());
private static boolean isRunning = false;
private static final String TAG = "Background";
#Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
class IncomingHandler extends Handler {
#Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_REGISTER_CLIENT:
mClients.add(msg.replyTo);
break;
case MSG_UNREGISTER_CLIENT:
mClients.remove(msg.replyTo);
break;
case MSG_SET_INT_VALUE:
position=msg.getData().getInt("int1");
break;
case MSG_SET_STRING_VALUE:
action=msg.getData().getString("str1");
if(action.equals("play")){
String[] proj = { MediaStore.Audio.Media.DATA,
MediaStore.Audio.Media.DURATION,
MediaStore.Audio.Media.IS_MUSIC,
MediaStore.Audio.Media.TITLE};
audioCursor = getContentResolver().query(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, proj,
MediaStore.Audio.Media.IS_MUSIC, null,
MediaStore.Audio.Media.TITLE + " ASC");
count = audioCursor.getCount();
audioCursor.moveToPosition(position);
int column_index = audioCursor.getColumnIndex(MediaStore.Audio.Media.DATA);
String path = audioCursor.getString(column_index);
startAudioPlayer(path);
playing=true;
if(playerActive)
sendBool(playing, "playing");
}else{
startAudioPlayer(action);
playing=true;
if(playerActive)
sendBool(playing, "playing");
}
action=null;
break;
case MSG_SET_BOOLEAN_VALUE:
pause=msg.getData().getBoolean("pause");
play=msg.getData().getBoolean("play");
stop=msg.getData().getBoolean("stop");
next=msg.getData().getBoolean("next");
back=msg.getData().getBoolean("back");
playerActive=msg.getData().getBoolean("playerActive");
if(pause){
mp.pause();
play=false;
playing=false;
sendBool(playing, "playing");
pause=false;
}
if(play){
pause=false;
mp.start();
playing=true;
sendBool(playing, "playing");
play=false;
}
default:
super.handleMessage(msg);
}
}
}
private void sendInt(int intvaluetosend) {
for (int i=mClients.size()-1; i>=0; i--) {
try {
Bundle b = new Bundle();
b.putInt("int1", intvaluetosend);
Message msg = Message.obtain(null, MSG_SET_INT_VALUE);
msg.setData(b);
mClients.get(i).send(msg);
} catch (RemoteException e) {
mClients.remove(i);
Log.d(TAG, "Int not send..."+e.getMessage());
}
}
}
private void sendString(String stringtosend) {
for (int i=mClients.size()-1; i>=0; i--) {
try {
Bundle b = new Bundle();
b.putString("str1", stringtosend);
Message msg = Message.obtain(null, MSG_SET_STRING_VALUE);
msg.setData(b);
mClients.get(i).send(msg);
} catch (RemoteException e) {
mClients.remove(i);
Log.d(TAG, "String not send..." +e.getMessage());
}
}
}
private void sendBool(boolean booltosend, String name) {
for (int i=mClients.size()-1; i>=0; i--) {
try {
Bundle b = new Bundle();
b.putBoolean(name, booltosend);
Message msg = Message.obtain(null, MSG_SET_BOOLEAN_VALUE);
msg.setData(b);
mClients.get(i).send(msg);
} catch (RemoteException e) {
mClients.remove(i);
Log.d(TAG, "Bool not send..." +e.getMessage());
}
}
}
#Override
public void onCreate() {
super.onCreate();
showNotification();
isRunning=true;
}
private void showNotification() {
nm = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
CharSequence text = getText(R.string.maintit);
Notification notification = new Notification(R.drawable.icon, text, System.currentTimeMillis());
PendingIntent contentIntent = PendingIntent.getActivity(this, 0, new Intent(this, Player.class), 0);
notification.setLatestEventInfo(this, getText(R.string.app_name), text, contentIntent);
nm.notify(R.string.app_name, notification);
}
#Override
public void onDestroy() {
//REMEMBER TO SAVE DATA!
if(mp.isPlaying())
mp.stop();
mp.release();
isRunning=false;
audioCursor.close();
nm.cancel(R.string.app_name);
super.onDestroy();
}
public static boolean isRunning()
{
return isRunning;
}
public void startAudioPlayer(String path){
try {
if(mp.isPlaying())
mp.reset();
mp.setDataSource(path);
} catch (IllegalArgumentException e) {
e.printStackTrace();
Log.d(TAG,e.getMessage());
} catch (IllegalStateException e) {
e.printStackTrace();
Log.d(TAG,e.getMessage());
} catch (IOException e) {
e.printStackTrace();
Log.d(TAG,e.getMessage());
}
try {
mp.prepare();
} catch (IllegalStateException e) {
e.printStackTrace();
Log.d(TAG,e.getMessage());
} catch (IOException e) {
e.printStackTrace();
Log.d(TAG,e.getMessage());
}
mp.start();
}
}
I hope someone can help, im getting very frustrated with this! Also, Im pretty sure there is no problem with the media player, I tested it before without the service... the cursors also work properly... Thing is... Do I need to necessarily call the service from the GUI for it to play the music?? What am I doing wrong?
EDIT: The website wont allow me to answer my own question so I post the solution here:
Ok, finally found a solution!
I read that the interaction with the service is only available once the onCreate method has finished... So, I added a Timer and filled it with the methods I needed to run:
new Timer().schedule(new TimerTask(){
public void run(){
sendBoolToService(playerActive, "playerActive");
sendIntToService(position);
sendStringToService(action);
}
}, 1000);
AND VOILA! It works! :D Hope its useful to someone!
What you need to do is to move the code in onCreate() which is dependent on the service being available to your onServiceConnected() method in your ServiceConnection implementation:
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
mService = new Messenger(service);
Toast.makeText(getApplicationContext(), "ATTACHED!", Toast.LENGTH_LONG).show();
try {
Message msg = Message.obtain(null, Background.MSG_REGISTER_CLIENT);
msg.replyTo = mMessenger;
mService.send(msg);
sendBoolToService(playerActive, "playerActive");
sendIntToService(position);
sendStringToService(action);
} catch (RemoteException e) {
Toast.makeText(getApplicationContext(), "Connection failed!", Toast.LENGTH_LONG).show();
}
}
public void onServiceDisconnected(ComponentName className) {
mService = null;
Toast.makeText(getApplicationContext(), "UNATTACHED!", Toast.LENGTH_LONG).show();
}
};
I would also look at your service implementation as I cannot understand why you are calling mService = new Messenger(service). Your IBinder instance should provide you with a mechanism for obtaining a reference to your service instance.
In my case, my issue was using android:process attribute for <service> element within Android Manifest, which is supposed to improve performance, but in reallity, maybe it does once the service is running, but it takes a very long while to reach onCreate() (and so also to reach onBind()). For me it was taking minutes. Now Apps and services run smooth and as expected.
I now this a very old question, but showing your Manifest file here makes sense.
More info:
https://developer.android.com/guide/topics/manifest/service-element

Categories