Android MediaPlayer reset freezes UI - java

I have a problem with the Android MediaPlayer when changing the dataSource of the player. According the specification of the MediaPlayer (http://developer.android.com/reference/android/media/MediaPlayer.html) I have to reset the player when changing the dataSource. This works fine, but as soon as the channelChanged method is called twice in quick succession the MediaPlayer.reset freezes the UI. I profile the code as seen here:
public void channelChanged(String streamingUrl)
{
long m1 = System.currentTimeMillis();
mMediaPlayer.reset();
long m2 = System.currentTimeMillis();
try
{
mMediaPlayer.setDataSource(streamingUrl);
}
catch (IOException e)
{
e.printStackTrace();
}
long m3 = System.currentTimeMillis();
mMediaPlayer.prepareAsync();
long m4 = System.currentTimeMillis();
Log.d("MEDIAPLAYER", "reset: " + (m2 - m1));
Log.d("MEDIAPLAYER", "setDataSource: " + (m3 - m2));
Log.d("MEDIAPLAYER", "preparing: " + (m4 - m3));
}
reset: 3
setDataSource: 1
preparing: 0
reset: 3119
setDataSource: 2
preparing: 1
So apparently the reset is blocked by the asynchronous preparing of the first call (when I wait until the first stream starts and then call channelChanged() again, everything is fine).
Any ideas how to solve the problems? Should I execute the whole method in a separate thread? Basically I want to avoid that, because it seems not to be a good coding style and can possibly cause some further issues, e.g. when the user tries to start the player again, but the player is still in the reset method, which on the other hand seems to wait for the asyncPrepare method. It is not clear how the player would behave...
Is there any other good solution?

MediaPlayer is a tricky bastard. I recommend you take a look at the sample app where the MediaPlayer bad design is made evident by looking at the mess of code you have to write around it to have a consistent media playback experience.
If anything, after looking at the sample, you see that when they want to skip a track, they essentially reset and release…
mPlayer.reset();
mPlayer.release();
…and later when they are ready to load a new track…
try {
mPlayer.reset();
mPlayer.setDataSource(someUrl);
mPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
#Override
public void onPrepared(MediaPlayer mediaPlayer) {
//bam!
}
});
mPlayer.prepareAsync();
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
I have added the try/catch because on some devices/OS versions, the MediaPlayer is worse than others and sometimes it just does weird stuff. You should have an Interface/Listener that is capable of reacting to these situations
UPDATE:
This is a method I use when I stop (or pause) my Music Playback (mostly taken from the sample app, this is running in a service and it has been modified to suit my own app but still).
The first method is used by both stop and pause, the former passes true, the later false
/**
* Releases resources used by the service for playback. This includes the "foreground service"
* status and notification, the wake locks and possibly the MediaPlayer.
*
* #param releaseMediaPlayer Indicates whether the Media Player should also be released or not
*/
void relaxResources(boolean releaseMediaPlayer) {
stopForeground(true);
stopMonitoringPlaybackProgress();
// stop and release the Media Player, if it's available
if (releaseMediaPlayer && mPlayer != null) {
mPlayer.reset();
mPlayer.release();
mPlayer = null;
}
// we can also release the Wifi lock, if we're holding it
if (mWifiLock.isHeld()) {
mWifiLock.release();
}
}
This is part of the processPauseRequest():
if (mState == State.Playing) {
// Pause media player and cancel the 'foreground service' state.
mState = State.Paused;
mPlayer.pause();
dispatchBroadcastEvent(ServiceConstants.EVENT_AUDIO_PAUSE);//notify broadcast receivers
relaxResources(false); // while paused, we always retain the mp and notification
And this is part of the processStopRequest() (simplified):
void processStopRequest(boolean force, final boolean stopSelf) {
if (mState == State.Playing || mState == State.Paused || force) {
mState = State.Stopped;
// let go of all resources...
relaxResources(true);
currentTrackNotification = null;
giveUpAudioFocus();
}
}
Now the core part is the next/skip…
This is what I do…
void processNextRequest(final boolean isSkipping) {
processStopRequest(true, false); // THIS IS IMPORTANT, WE RELEASE THE MP HERE
mState = State.Retrieving;
dispatchBroadcastEvent(ServiceConstants.EVENT_TRACK_INFO_LOAD_START);
// snipped but here you retrieve your next track and when it's ready…
// you just processPlayRequest() and "start from scratch"
This is how the MediaPlayer sample does it (found in the samples folder) and I haven't had problems with it.
That being said, i know what you mean when you say you get the whole thing blocked, I've seen it and it's the MP buggyness. If you get an ANR I'd like to see the log for it.
For the record here's how I "begin playing" (a lot of custom code has been omited but you get to see the MP stuff):"
/**
* Starts playing the next song.
*/
void beginPlaying(Track track) {
mState = State.Stopped;
relaxResources(false); // release everything except MediaPlayer
try {
if (track != null) {
createMediaPlayerIfNeeded();
mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mPlayer.setDataSource(track.audioUrl);
} else {
processStopRequest(true, false); // stop everything!
return;
}
mState = State.Preparing;
setUpAsForeground(); //service
/* STRIPPED ALL CODE FROM REMOTECONTROLCLIENT, AS IT ADDS A LOT OF NOISE :) */
// starts preparing the media player in the background. When it's done, it will call
// our OnPreparedListener (that is, the onPrepared() method on this class, since we set
// the listener to 'this').
// Until the media player is prepared, we *cannot* call start() on it!
mPlayer.prepareAsync();
// We are streaming from the internet, we want to hold a Wifi lock, which prevents
// the Wifi radio from going to sleep while the song is playing.
if (!mWifiLock.isHeld()) {
mWifiLock.acquire();
}
} catch (IOException ex) {
Log.e("MusicService", "IOException playing next song: " + ex.getMessage());
ex.printStackTrace();
}
}
As a final note, I've noticed that the "media player blocking everything" happens when the audio stream or source is unavailable or unreliable.
Good luck! Let me know if there's anything specific you'd like to see.

The newest phones and Android API works much butter, reset method takes only 5-20 ms when fast switching between songs (next or prev)
So there is no solution for older phones, it just how it works

Related

How to not mute the playing system music while using SipAudioCall voice call?

Requirement: Android uses native android.net.sip to implement Internet telephony, and does not affect the playing system music player when answering a call.
Phenomenon: When SipAudioCall calls the startAudio method, the system music player will be muted.
Question: How can I play music while talking? Or the receiving party only turns on the microphone without making the call sound.
I tried not to call the startAudio method, but I can't talk normally with the microphone. I can't find other Api implementations of SipAudioCall that only open the microphone.
Code:
public void answer() {
try {
if (mSipAudioCall == null) {
Timber.tag(TAG).e("answer mSipAudioCall:null");
return;
}
mSipAudioCall.answerCall(25);
// As soon as the system music is called here, it will be muted, but will not stop
mSipAudioCall.startAudio();
mSipAudioCall.setSpeakerMode(false);
if (mSipAudioCall.isMuted()) {
mSipAudioCall.toggleMute();
}
} catch (SipException e) {
e.printStackTrace();
Timber.tag(TAG).e(e, "answer :" + e.getMessage());
}
}

How to stop a WAV clip in JAVA from playing

I am trying to stop an intro song from playing when pressing the start button. I tried doing so using this code. Note that this code does not entail all my code. The GUI looks fine, the Actionlisteners work fine too. Only the music does not stop playing when the start button is pressed.
File introPath = new File("src/BattleshipGUI/423499__soundflakes__epic-heroic-orchestral-
dramatic.wav");
File buttonPressedPath = new File("src/BattleshipGUI/sfx_patcher_button_launch.wav");
static Clip introWAV;
Menu() {
super("BattleBoard");
this.setContentPane(this.panelMain);
this.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
this.setLocationRelativeTo(null);
this.pack();
play(introPath); // playing when launching
// when the game starts, the sound should stop
ButtonStartGame.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
super.mouseClicked(e);
play(buttonPressedPath);
try {
if (random) {
currentCols = (Integer) spinnerColumns.getValue();
currentRows = (Integer) spinnerRows.getValue();
if (currentCols < 5 || currentRows < 5) {
throw (new IllegalArgumentException());
} else {
BoardFrame b = new BoardFrame(currentRows, currentCols);
b.SetFrame(currentRows, currentCols);
b.AddRandomShips(currentRows, currentCols);
b.ScoreMethod(adjustedScoreMethod);
introWAV.stop();
introWAV.flush();
introWAV.close();
dispose();
public static void SetIntroWAV(Clip clip){
introWAV=clip;
}
public static void play(File file) {
try {
Clip sound = AudioSystem.getClip();
sound.open(AudioSystem.getAudioInputStream(file));
SetIntroWAV(sound);
sound.start();
} catch (Exception e) {
System.out.println(e);
}
}
I tried other ways, like using while loops in the Play-class, 'if-else'-statements,... Does someone know how to fix this? Thanks in advance!
The culprit is part of your play method.
Whenever you want to play any sound you also call SetIntroWAV internally. This results in your introWAV variable being set.
Here's why that's a problem:
The first time you call play, your intro sound is played back and introWAV has the correct value.
However, once you start your game and play a different sound (namely using buttonPressedPath) your introWAV variable is set to a different value: the sound that was most recently started.
When you then try to stop your sound from playing, you're using introWAV which doesn't actually contain a reference to your intro sound anymore. Instead, this will result in your most recently played sound to be stopped since this is what introWAV is holding now.
To fix this, it's simply a case of only setting your introWAV variable once and not every time play is called. There are multiple ways of doing this, including these:
You could let your play method return the resulting Clip that will be played afterwards:
public static Clip play(File file) {
Clip sound = null;
try {
sound = AudioSystem.getClip();
sound.open(AudioSystem.getAudioInputStream(file));
sound.start();
} catch (Exception e) {
System.out.println(e);
} finally {
return sound;
}
}
You can then use this returned value to call SetIntroWAV once: SetIntroWAV(play(introPath));
You could also use this return value for other purposes like keeping local references to your sounds. However, you don't have to use it every time and can still ignore it whenever you don't need that reference.
You could rewrite your play method to also contain a parameter telling the method whether the sound you're trying to play is the intro sound:
public static void play(File file, boolean intro) {
try {
Clip sound = AudioSystem.getClip();
sound.open(AudioSystem.getAudioInputStream(file));
if(intro) {
SetIntroWAV(sound);
}
sound.start();
} catch (Exception e) {
System.out.println(e);
}
}
This will also result in SetIntroWAV only being called once.
I'd also recommend you use more of an object-oriented style of programming for this as it can make things like these much more obvious and easier to fix.
For example, you could create separate classes for audio playback and your gameplay.
IMHO the best practice with Clip variables is to load and open, and then hold them in memory. This can be done in a class that manages your sound effects. In that class, have a Clip as an instance variable and preload and open it in the constructor.
This class can also have two methods that are called from your game.
public void play() {
clip.setFramePosition(0); // ensures Clip will start from the beginning
clip.start();
}
public void stop() {
clip.stop();
}
With this sort of structure, it also become easier to manage multiple sounds. For example, you can have two instances of this sound-managing class, and set each to a different sound source. Then, you can readily stop one and start another.

Destroy Nuance session

I have an activity that creates an "Audio" class and tries to use android Text to Speech API to read some text. If the language is not supported, it tries to use MediaPlayer to play a custom mp3 file from the server. Finally if MediaPlayer fails, it uses Nuance SpeechKit to read the text:
My problem is when I destroy the activity, I want to destroy/stop the Nuance audio too and I'm not sure how to shutdown Nuance audio.
Activity class
private Audio audio;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.my_layout);
audio = new Audio(this).play("my text to read");
}
#Override
protected void onPause() {
audio.pause();
super.onPause();
}
#Override
protected void onDestroy() {
audio.destroy();
super.onDestroy();
}
Audio class
private TextToSpeech tts;
private MediaPlayer player;
private Session session;
public void play(String text) {
// check if supported
if (supported) tts.speak(text, TextToSpeech.QUEUE_FLUSH, null);
else mediaPlayer(text);
}
private void mediaPlayer(String text) {
// make some queries on server to find the file url
if (queryFoundFile) {
player = new MediaPlayer();
player.setDataSource(myFileUrl);
player.setAudioStreamType(3);
player.prepare();
player.start();
} else nuancePlayer(text);
}
private void nuancePlayer(String text) {
Transaction.Options options = new Transaction.Options();
options.setLanguage(new Language("eng-USA"));
session = Session.Factory.session(activity, myServer, appKey);
session.speakString(text, options, new Transaction.Listener() {
#Override
public void onError(Transaction transaction, String s, TransactionException e) {
e.printStackTrace()
}
});
// it reaches here and nuance plays the audio
}
// these are the methods I call when the activity is paused or destroyed
public void pause() {
if (tts != null) tts.stop();
if (player != null) player.stop();
if (nuance != null) nuance.getAudioPlayer().stop(); // don't work
}
public void destroy() {
if (tts != null) tts.shutdown();
if (player != null) player.release();
if (nuance != null) nuance.getAudioPlayer().stop(); // don't work
}
If I'm using Text to Speech or MediaPlayer and if I destroy my Activity, the audio is immediately destroyed. But I can't seem to destroy the audio if is Nuance playing. It just keeps talking.
I did some debugging and the pause() and destroy() methods are being called. Also nuance.getAudioPlayer is not null and is the AudioPlayer playing. I can't find the reason why he is not stopping when I call the method stop() on him.
What is Nuance?
This is my first time using Nuance so I am not that experienced with this. Basically I see it like an alternative to the Android Text to Speech.
Nuance Developers
Why I have this on my project?
My project has 4 main languages, and I need to have a text to speech function to read some text. The problem is, android Text to Speech don't support some of these languages which Nuance support.
Why is Nuance my last option?
Because Nuance has costs. I try to use android TTS or MediaPlayer. Only if those two fail, I use Nuance. It is a last resort to read my text!
As per change log, this issue is known since a year ago and no fixed yet (as per changelog).
For temporary solution till they gave you fixed release you can do the following:
Break your text in small chunks and instead of playing complete text(as audio) at once, Queued these small text chunks into the audio player so that you audio will stop after finish playing the current chunk instead of complete text.
because as per known issue in the change log given below:
Please note this line:
However, if multiple Audios are queued for playback and stop() is called, then the next Audio will not begin playing and the queue will be cleared.
I hope this will help you.
A quick look on the nuance API shows that session.speakString(...) returns a Transaction, and a Transaction has 2 methods that might be of interest for you:
cancel() - Cancel the Transaction.
stopRecording() - Stop recording audio and complete the Transaction, if there is an ongoing recording.
Looks like cancel() is what you need.
Try setting session to null to see if the garbage collector solves the problem.

Android NotificationListenerService: how to know if user clicked on the notification (opening the related app) or simply deleted it

I'm working with notifications generated by every app (not only mine) on my Android device (android 5.1.1).
By extending NotificationListenerService I'm able to know when a push notification is posted (overriding the "onNotificationPosted" method) and when a notification is removed (overriding the "onNotificationRemoved" method).
The problem is that I would like to know how the notification was removed:
a) by clicking it (so opening the app)
or
b) by swyping it (so it is only removed)
?
Is it possible to know it?
Thank you in advance!
The best way to do it is to get the list of all running processes!
So, in the onNotificationRemoved method we can:
1. obtain the list of running processes using the Android Processes library
2. compare each process name with the packageName
3. if the comparison return a true value, we check if the process is in foreground
public void onNotificationRemoved(StatusBarNotification sbn) {
String packageName = sbn.getPackageName();
try {
List<AndroidAppProcess> processes = ProcessManager.getRunningAppProcesses();
if (processes != null) {
for (AndroidAppProcess process : processes) {
String processName = process.name;
if (processName.equals(packageName)) {
if (process.foreground ==true)
{
//user clicked on notification
}
else
{
//user swipe notification
}
}
}
}
}
catch (Exception e)
{
String error = e.toString();
}
}

How to change the output file of a mediarecorder without stopping the mediarecorder

I have a requirement in my project, where video is being recorded and uploaded to the server, but since mobile networks are not reliable, at the beginning what I decided to do was every 30 secs
stop the recorder
reset the recorder state
retrieve the file written to by the recorder and upload (multipart form data) it in a different thread.
change the outfile of the recorder to a new file based on the hash of the current timestamp.
repeat process every 30 secs
Doing this suits my needs perfectly as each of the 30sec video file sizes are not more than 1MB and upload happens smoothly.
But the problem I am facing is that every time the media recorder stops and starts again there is a delay of about 500ms, so the video that I receive at the server has these 500ms breaks every 30secs which is really bad for my current situation, so I was thinking if it would be possible to just change the file that the recorder is writing to on the fly?
Relevant code:
GenericCallback onTickListener = new GenericCallback() {
#Override
public void execute(Object data) {
int timeElapsedInSecs = (int) data;
if (timeElapsedInSecs % pingIntervalInSecs == 0) {
new API(getActivity().getApplicationContext()).pingServer(objInterviewQuestion.getCurrentAccessToken(),
new NetworkCallback() {
#Override
public void execute(int response_code, Object result) {
// TODO: HANDLE callback
}
});
}
if (timeElapsedInSecs % uploadIntervalInSecs == 0 && timeElapsedInSecs < maxTimeInSeconds) {
if (timeElapsedInSecs / uploadIntervalInSecs >= 1) {
if(stopAndResetRecorder()) {
openConnectionToUploadQueue();
uploadQueue.add(
new InterviewAnswer(0,
objInterviewQuestion.getQid(),
objInterviewQuestion.getAvf(),
objInterviewQuestion.getNext(),
objInterviewQuestion.getCurrentAccessToken()));
objInterviewQuestion.setAvf(MiscHelpers.getOutputMediaFilePath());
initializeAndStartRecording();
}
}
}
}
};
here is initializeAndStartRecording() :
private boolean initializeAndStartRecording() {
Log.i("INFO", "initializeAndStartRecording");
if (mCamera != null) {
try {
mMediaRecorder = CameraHelpers.initializeRecorder(mCamera,
mCameraPreview,
desiredVideoWidth,
desiredVideoHeight);
mMediaRecorder.setOutputFile(objInterviewQuestion.getAvf());
mMediaRecorder.prepare();
mMediaRecorder.start();
img_recording.setVisibility(View.VISIBLE);
is_recording = true;
return true;
} catch (Exception ex) {
MiscHelpers.showMsg(getActivity(),
getString(R.string.err_cannot_start_recorder),
AppMsg.STYLE_ALERT);
return false;
}
} else {
MiscHelpers.showMsg(getActivity(), getString(R.string.err_camera_not_available),
AppMsg.STYLE_ALERT);
return false;
}
}
Here is stopAndResetRecorder:
boolean stopAndResetRecorder() {
boolean success = false;
try {
if (mMediaRecorder != null) {
try {
//stop recording
mMediaRecorder.stop();
mMediaRecorder.reset();
mMediaRecorder.release();
mMediaRecorder = null;
Log.d("MediaRecorder", "Recorder Stopped");
success = true;
} catch (Exception ex) {
if(ex != null && ex.getMessage()!=null && ex.getMessage().isEmpty()){
Crashlytics.log(Log.ERROR, "Failed to stop MediaRecorder", ex.getMessage());
Crashlytics.logException(ex);
}
success = false;
} finally {
mMediaRecorder = null;
is_recording = false;
is_recording = false;
}
}
} catch (Exception ex) {
success = false;
}
Log.d("MediaRecorder", "Success = " + String.valueOf(success));
return success;
}
You can speed it up slightly by not calling the release() method and all of the rest of the destruction that you do in stopAndResetRecorder() (see the documentation for the MediaRecorder state machine).
You also don't need to call both stop() and reset().
You could instead have an intermediate resetRecorder() function which just performed reset() then call initializeAndStartRecording(). When you finish all of your recording, you could then call stopRecorder() which would perform the destruction of your mMediaRecorder.
As I say, this will save you some time, but whether the extra overhead you currently have of destroying and re-initialising the MediaRecorder is a significant portion of the delay I don't know. Give that a try, and if it doesn't fix your problem, I'd be interested to know how much time it did/didn't save.
It seems to me that the setOutputFile calls a native method regarding to MediaRecorder's source, so I don't think there's an easy way to write into seperate files at the same time.
What about uploading it in one chunk at the end, but allow the user to do anything after starting the uploading process? Then the user wouldn't notice how much time the upload takes, and you can notify him later when the upload successed/failed.
[Edit:] Try to stream upload to server, where the server does the chunking mechanism to seperate files. Here you can have a brief explanation how to do so.
Apparently MediaRecorder.setOutputFile() also accepts a FileDescriptor.
So, if you were programming at low level (JNI) you could have represented a process's input stream as a file descriptor and in turn, had that process write to different files when desired. But that would involve managing that native "router" process from java.
Unfortunately, on java API side, you are out of luck.

Categories

Resources