I have a MediaPlayer (.wav file) that sometimes needs to be repeated in rapid succession. If it's already playing, I restart it:
if (player.isPlaying()) {
player.pause();
player.seekTo(0);
}
player.start();
The problem is that when the MediaPlayer is interrupted at some random position, there's often a tiny but noticeable scratchy noise at the end.
One solution I've tried is to create an array of MediaPlayers that load the same wav file, cycle through them, never interrupt, and ignore the call if the current
item is already playing (which happens rarely, and the missed call isn't noticed in the general barrage anyway).
MediaPlayer player = players.get(current);
if (!player.isPlaying())
player.start();
if((++current) >= players.size())
current = 0;
This gets rid of the noise, but the solution is kind of ugly. Is there a better way?
The noise is probably the waveform being cut mid-cycle. It can possibly be fixed with calls to setVolume before stopping and starting. Having multiple MediaPlayers isn't a terrible idea, but you may want to have a look at SoundPool.
Related
I'm creating a narrative application in which I need to play videos.
When playing a media with MediaPlayer.playMedia(Media media).
In order to avoid the black background I wait the wait for the playing event before showing the media player.
player.addMediaPlayerEventListener(new MediaPlayerEventAdapter() {
#Override
public void playing(MediaPlayer mediaPlayer) {
showVideo();
}
}
The problem is that this event is triggered before the video actually starts playing, so it still displays black for a little while before.
How can I find a way around this? Thanks
I don't know for certain, but you could try some other event listener methods - perhaps the videoOutput, elementaryStreamAdded or elementaryStreamSelected (and check the stream type parameter was video), or maybe wait for the very first positionChanged event (whilst not ideal, it might work well enough for your use case).
For a more detailed version of this question, please see How to make MediaPlayer wait for ObjectAnimator (and generally control Android UI sequence)? Thanks. - Code-Read
I am attempting to build an Android app that starts up with this sequence:
Zoom its main View in (a TextView);
After the view is fully zoomed in, emit a sound;
After the sound is emitted, execute the main program.
I first tried simply entering the zoom code (ObjectAnimator) before the sound code (MediaPlayer) in my program. But the code does not run in order of appearance. MediaPlayer plays in parallel with ObjectAnimator, regardless of code order. Why this is happening and how do I prevent it?
Attempting to cope, I have applied various timing and locking approaches to make MediaPlayer wait on ObjectAnimator. I've searched much here and elsewhere for solutions. So far the only ones I've found are:
Run MediaPlayer from a Handler().postDelayed, and
Have AnimatorListenerAdapter() launch MediaPlayer when the animation completes.
These both work, but (1) isn't precise as I must hard code the amount of time to wait (and manage this value manually), and (2) isn't general and the main application code still starts before MediaPlayer finishes. Numerous Answers recommend using AsyncTask to control Android UI sequences, but AsyncTask only applies to two tasks, waiting on one in a background thread, which really isn't appropriate with sequences of more than two stages.
Android - wait for animation to finish before continuing? is on this problem also. The accepted answer is AnimatorListenerAdaptor(), but the OP asks, That works for now but if I then want more lines of code to run after the animation has finished do I have to basically have my whole code in the onAnimationEnd method? and I agree - this does not seem reasonable.
With further testing, it appears my question might be simplified to: How do I make subsequent routines wait for ObjectAnimator?
The code below illustrates this latter question. Boolean waitLock is set to true. Then ObjectAnimator is launched. Then a while() loop waits for waitLock to be set to false by onAnimationEnd(). ObjectAnimator never completes, waitLock is never set to false, and the while() loops indefinitely. Why?
However, if waitLock is set to false initially, ObjectAnimator completes and the application exits normally. Why does ObjectAnimator complete in this case but not in the other?
public class MainActivity extends Activity {
private boolean waitLock;
View mainView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mainView = findViewById(R.id.mainView);
waitLock = true;
ObjectAnimator scalexAnim = ObjectAnimator.ofFloat(mainView, "scaleX", 0f, 1f);
scalexAnim.setDuration(1500);
scalexAnim.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationStart(Animator animator) {
Log.w("sA", "onAnimationStart reached");
super.onAnimationStart(animator);
}
#Override
public void onAnimationEnd(Animator animation) {
Log.w("sA", "onAnimationEnd reached");
waitLock = false;
super.onAnimationEnd(animation);
}
});
Log.w("zI", "starting.."); zoomIn.start(); Log.w("zI", "started");
while (waitLock == true) {
SystemClock.sleep(1000);
Log.w("while(waitLock)", "sleep 1000ms");
}
Log.w("onCreate", "exit");
}
Here is corresponding logcat output when waitLock is set to true:
07-02 13:34:14.850 9233-9233/com.code_read.sequentialcontroltest W/sA﹕ starting..
07-02 13:34:14.860 9233-9233/com.code_read.sequentialcontroltest W/sA﹕ onAnimationStart reached
07-02 13:34:14.860 9233-9233/com.code_read.sequentialcontroltest W/sA﹕ started
07-02 13:34:15.861 9233-9233/com.code_read.sequentialcontroltest W/while(waitLock)﹕ sleep 1000ms
07-02 13:34:16.862 9233-9233/com.code_read.sequentialcontroltest W/while(waitLock)﹕ sleep 1000ms
07-02 13:34:17.863 9233-9233/com.code_read.sequentialcontroltest W/while(waitLock)﹕ sleep 1000ms
07-02 13:34:18.864 9233-9233/com.code_read.sequentialcontroltest W/while(waitLock)﹕ sleep 1000ms
Since nobody has answered this, I'm posting what I've found so far. However, I consider this question still open. I certainly don't know all there is to know about Android or Java, and I'm hopeful there's a better answer.
My findings are:
Generally, the Android platform and native libraries are not
friendly toward synchronized programming. I found numerous
questions (see links below) asking how to make various operations
happen in a controlled sequence, and no very satisfactory general
method of doing so;
AsyncTask is limited to a single pair of events, and cannot be reused within an application;
Looper and Handler looked promising for awhile, but apparently Looper does not wait for events to finish before dequeuing further ones;
It appears that visual routines are more easily synchronized than audio routines, possibly because Android's visual library calls are more mature than audio. I found many comments indicating problems synchronizing Android's audio routines such as MediaPlayer or AudioTrack. Often the recommended solution where timing is important is to go with compiled C code and specialized libraries;
Locking mechanisms are geared toward use by separate threads. My attempts to employ them as a way of controlling sequence within a single thread always failed with deadlock;
Conversely, only code in the UI thread can alter the screen or generate a sound. This leads to a paradox to which the only solution I found was either tightly controlling the UI thread's Looper queue, or coding interleaves between audio and visual function calls;
Where in the Android "life cycle" I placed my code made no difference. E.g., moving code to be executed later in time from onCreate() to onResume() made no difference.
Given the above, below is my coded solution to the original problem of animating a View, then emitting a sound, then running the rest of the program. It relies on callbacks, which cause a curious inversion: code earlier in the source file runs later in time. I find this disturbing from the perspective of legibility and manageability. I also dislike the generally convoluted structure, but so far this is coded as plainly as I know how.
(I suspect these sync issues were masked by single core CPUs. Now that most Android hardware is multicore, the OS may have some catching up to do. I would like to see something like a #Synchronous class that would designate sections of code to run serially in the order they appear. Or maybe a way to specify that sections of code run on a specific core only).
Links to a few of the many related questions:
How do I make Java wait for a method to finish before continuing?
How do I do something when an animation finishes?
Android: trying to add a wait in between two animations, getting Object not locked by thread before wait() error
Waiting for a function to finish execution and using the results
Android how to wait for code to finish before continuing
Android - AnimatorSet, Object Animator - Bounce animation chain is amalgamating?
My coded solution. Notice how the sequence of execution is reversed. rotateZoomin() runs first, then MediaPlayer(), then playTracks():
MediaPlayer.OnCompletionListener smComplete = new MediaPlayer.OnCompletionListener() {
#Override
public void onCompletion(MediaPlayer mediaPlayer) {
startupMediaPlayer.stop();
startupMediaPlayer.reset();
startupMediaPlayer.release();
playTracks(); // This is third to execute (the rest of my application)
}
};
startupMediaPlayer = MediaPlayer.create(this, R.raw.doorbell2mp3);
startupMediaPlayer.setOnCompletionListener(smComplete);
ObjectAnimator rotateAnim = ObjectAnimator.ofFloat(mainView, "rotation", 1080f, 0f);
ObjectAnimator scalexAnim = ObjectAnimator.ofFloat(mainView, "scaleX", 0f, 1f);
ObjectAnimator scaleyAnim = ObjectAnimator.ofFloat(mainView, "scaleY", 0f, 1f);
AnimatorSet rotateZoomIn = new AnimatorSet();
rotateZoomIn.play(rotateAnim).with(scalexAnim);
rotateZoomIn.play(rotateAnim).with(scaleyAnim);
rotateZoomIn.setDuration(1500);
rotateZoomIn.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
startupMediaPlayer.start(); // This is second to execute
}
});
rotateZoomIn.start(); // This is first to execute
I was wondering if releasing my media player before I play a random sound is bad practice:
So I don't usually deal with media output too much, but I am making a simple app that plays a random sound every time a button is clicked (sounds [] is an array filled with raw media files)
public void onClick(View v){
if(mediaplayer != null){
mediaplayer.release();
}
mediaplayer = MediaPlayer.create(this, sounds[randomNum])
mediaplayer.start();
}
So my question is, would releasing my media player every time before creation be considered good/bad practice? Would there be any better way to do this, as releasing and re-initializing the MediaPlayer object seems like it would consume resources...
Thanks,
Ruchir
You typically use release() when you no longer want to use a MediaPlayer any more. Once you call that, it can never be used again. It effectively destroys the native components that back its functionality.
If you do release, you will have to prepare the media all over again the next time you want to play it. This can be a time consuming process. If you want a sound to play responsively to a button press, you probably don't want to have to prepare it each time.
My app plays a coin sound every time a button is pressed.
coin_sound.start();
You can easily press faster than the coin sound. When this happens I want the coin sound to start from the beginning ever time the button is pressed.
if(coin_sound.isPlaying()){
coin_sound.reset();
coin_sound = MediaPlayer.create(getContext(), R.raw.coin02);
}
coin_sound.start();
The problem with this is that loading a media file tiny as it may be is still a relatively slow process. When you start to click the button really fast the app lags hard.
Are there any solutions to my problem? The only idea I have is to do something with an array of coin_sounds, but this method seems like it will be messy and gross...
The other answer posted here is somewhat correct. You should not call create over and over.
The code in that answer has a problem, though. The reset method sends the MediaPlayer into the idle state, where it is illegal to call most other methods. If you were to go that route, you have to call methods in the following order:
coin_sound.reset();
coin_sound.setDataSource(...);
coin_sound.prepare();
coin_sound.start();
The difference between calling create and the previous sequence of method calls is simply the creation of a new instance. That, however, is not the quickest way to do what should be done.
You should simply call coin_sound.seekTo(0); when you want the current playing sound to restart. So do something like:
if (coin_sound.isPlaying()) coin_sound.seekTo(0);
else coin_sound.start();
That assumes you have left the MediaPlayer in the prepared state so start can be called. You can accomplish that by calling reset, setDataSource, and prepare in the onCompletion listener. Also, make sure to call release when the sound is no longer needed.
It is because you are initiating coin_sound in the button click event, try this
initiate this variable in your oncreate method
coin_sound = MediaPlayer.create(getContext(), R.raw.coin02);
then make this your code for your button
if(coin_sound.isPlaying()){
coin_sound.reset();
}
coin_sound.start();
the problem is you are recreating a new media player each time the button is clicked so the new media player doesnt think there is a sound
and do you need to start it again with coin_sound.start();? doesnt restart stop then start the sound for you?
I have been experimenting with PulpCore, trying to create my own tower defence game (not-playable yet), and I am enjoying it very much I ran into a problem that I can't quite figure out. I extended PulpCore with the JOrbis thing to allow OGG files to be played. Works fine. However, pulpCore seems to have a problem with looping the sound WHILE animating the volume level. I tried this with wav file too, to make sure it isn't jOrbis that breaks it. The code is like this:
Sound bgMusic = Sound.load("music/music.ogg");
Playback musicPlayback;
...
musicVolume = new Fixed(0.75);
musicPlayback = bgMusic.loop(musicVolume);
//TODO figure out why it's NOT looping when volume is animated
// musicVolume.animate(0, musicVolume.get(), FADE_IN_TIME);
This code, for as long as the last line is commented out, plays the music.ogg again and again in an endless loop (which I can stop by calling stop on the Playback object returned from loop(). However, I would like the music to fade in smoothly, so following the advice of the PulpCore API docs, I added the last line which will create the fade-in but the music will only play once and then stop. I wonder why is that? Here is a bit of the documentation:
Playback
pulpcore.sound.Sound.loop(Fixed level)
Loops this sound clip with the
specified volume level (0.0 to 1.0).
The level may have a property
animation attached.
Parameters: level
Returns: a Playback object for this
unique sound playback (one Sound can
have many simultaneous Playback
objects) or null if the sound could
not be played.
So what could be the problem? I repeat, with the last line, the sound fades in but doesn't loop, without it it loops but starts with the specified 0.75 volume level.
Why can't I animate the volume of the looped music playback? What am I doing wrong? Anyone has any experience with pulpCore and has come across this problem? Anyone could please download PulpCore and try to loop music which fades-in (out)?
note: I need to keep a reference to the Playback object returned so I can kill music later.
If the animate method only sets the option, it can work unpredictably - try switching this line with the loop itself, so the animation applies first.
Can you animate the volume on an unlooped playback, and then, at the end of that playback, start the loop at the fixed level?
Finally I managed to get an explanation and a simple work-around for this issue from the pulp core author. So here it is:
It is a PulpCore bug. When the output
volume is zero, the sound player stops
looping the sound.
To work around it, animate from a
value that is not zero, like this:
musicVolume.animate(0.0001, 1, FADE_IN_TIME);
Link to this on pulpcore Google groups