I have an app with buttons that play various frame-by-frame animations when pressed. The animations are stored as sequences of .png files. Some of the animations fill a small area, and they seem to play fine using animationDrawable. However, some of the animations fill a large part of the screen (even though they are mostly transparent using an alpha channel), and I get an out of memory error when the app loads on my phone (but not the emulator). I can't play them as video files because they need to overlay the main screen.
My main question - is there a way to play large png sequence animations (Say 60 frames at 320x480) using animationDrawable?
So far in my research there doesn't seem to be any way around the memory limitations... so I have been looking into making my own method that creates an ImageView and populates it with a bitmap, and then successively replaces the bitmap with each frame after a delay. However, with this technique my animation seems to only play the first and last frames.
Here is the code of my stab at using animationDrawable - the "chimp" plays fine, but when I add the "bubbles" (the larger images), the app doesn't load at all - logCat shows: "ERROR/dalvikvm-heap(3248): 1008000-byte external allocation too large for this process... ERROR/AndroidRuntime(3248): java.lang.OutOfMemoryError: bitmap size exceeds VM budget
"
public class Babymode_tst extends Activity implements OnClickListener{
/** Called when the activity is first created. */
AnimationDrawable chimpAnimation;
AnimationDrawable bubblesAnimation;
private MediaPlayer mp;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
setVolumeControlStream(AudioManager.STREAM_MUSIC);
final ImageButton chimpButton = (ImageButton) findViewById(R.id.chimp_button);
chimpButton.setOnClickListener(this);
ImageView chimpImage = (ImageView) findViewById(R.id.chimp_imageview);
chimpImage.setBackgroundResource(R.drawable.chimp_anim);
chimpAnimation = (AnimationDrawable) chimpImage.getBackground();
final ImageButton bubblesButton = (ImageButton) findViewById(R.id.bubbles_button);
bubblesButton.setOnClickListener(this);
ImageView bubblesImage = (ImageView) findViewById(R.id.bubbles_imageview);
bubblesImage.setBackgroundResource(R.drawable.bubbles_anim);
bubblesAnimation = (AnimationDrawable) bubblesImage.getBackground();
}
public void onClick(View v) {
if (mp != null) {
mp.release();
}
Random generator = new Random();
int randomIndex;
switch(v.getId()){
case R.id.chimp_button:
randomIndex = (generator.nextInt( 2 ) + 1);
if (randomIndex == 1){
mp = MediaPlayer.create(this, R.raw.chimp_1);
}
if (randomIndex == 2){
mp = MediaPlayer.create(this, R.raw.chimp_2);
}
mp.start();
chimpAnimation.setVisible(true,true);
chimpAnimation.start();
break;
case R.id.bubbles_button:
randomIndex = (generator.nextInt( 3 ) + 1);
if (randomIndex == 1){
mp = MediaPlayer.create(this, R.raw.bubbles_1);
}
if (randomIndex == 2){
mp = MediaPlayer.create(this, R.raw.bubbles_2);
}
if (randomIndex == 3){
mp = MediaPlayer.create(this, R.raw.bubbles_3);
}
mp.start();
bubblesAnimation.setVisible(true,true);
bubblesAnimation.start();
break;
}
}
}
And here is my attempt at playing the frames one by one in an ImageView, but it only plays the first and last frames:
public void playAnimation(String name){
final ImageView bubblesImageView = (ImageView) findViewById(R.id.bubbles_imageview);
bubblesImageView.setImageResource(R.drawable.bubbles_anim_v5_0003);
mHandler.postDelayed(new Runnable() {
public void run() {
bubblesImageView.setImageResource(R.drawable.bubbles_anim_v5_0015);
}
}, 500);
mHandler.postDelayed(new Runnable() {
public void run() {
bubblesImageView.setImageResource(R.drawable.bubbles_anim_v5_0025);
}
}, 500);
mHandler.postDelayed(new Runnable() {
public void run() {
bubblesImageView.setImageResource(R.drawable.bubbles_anim_v5_0035);
}
}, 500);
}
Any help would be appreciated - thanks
your mHandler.postDelayed(new Runnable(){...}); are all triggering at the same time e.g. after 500 ms, you need them to trigger one after another by either
nHandler.postDelayed(new Runnable() {
public void run() {
bubblesImageView.setImageResource(R.drawable.bubbles_anim_v5_0015);
}
}, 500);
mHandler.postDelayed(new Runnable() {
public void run() {
bubblesImageView.setImageResource(R.drawable.bubbles_anim_v5_0025);
}
}, 1000);
mHandler.postDelayed(new Runnable() {
public void run() {
bubblesImageView.setImageResource(R.drawable.bubbles_anim_v5_0035);
}
}, 1500);
or having each runnable queue up the next one after a 500 ms delay e.g.
mHandler.postDelayed(new Runnable() {
public void run() {
bubblesImageView.setImageResource(R.drawable.bubbles_anim_v5_0015);
mHandler.postDelayed(new Runnable() {
public void run() {
bubblesImageView.setImageResource(R.drawable.bubbles_anim_v5_0025);
}
}, 1000);
}
}, 500);
Or you could construct an array of ìnt with the R.drawable.xxx value and every 500 ms just display the next image.
This is an extremely difficult thing to do, but it is doable. The key is to recycle your bitmaps right after you use them. Check out this wrapper class someone wrote that will handle all that for you:
http://snippetrepo.com/snippets/android-animation-without-outofmemoryerror
In my company we got the same problem, we've tried to make a splash screen in this way(and it was total disaster). If you want so hard to got some kind of animation, generate video from the PNG frames (i think that FFMPEG can do it), and then play the video.
Related
Im trying to get the seekbar to update and show progress whenever I play an mp3 with mediaPlayer. Music plays fine everytime, seekbar will always snap to 0 position when mp3 is playing.
I was trying to follow this answer but it just wont work... SeekBar and media player in android
I have this code in main activity
private Handler mHandler = new Handler();
MainActivity.this.runOnUiThread(new Runnable() {
#Override
public void run() {
if (Assets.mediaPlayer != null) {
int mCurrentPosition = Assets.mediaPlayer.getCurrentPosition() / 1000;
if(OnionFragment.seekBar!= null) {
OnionFragment.seekBar.setProgress(mCurrentPosition);
}
}
mHandler.postDelayed(this, 1000);
}
});
in OnionFragment I have
public static SeekBar seekBar;
seekBar = (SeekBar) rootView.findViewById(R.id.seekBar);
and in OnionFragments onclick (the play button)I have
Assets.playMusicFile(MainActivity.items.get(MainActivity.selectedItem).getSongId(), true);
if(Assets.mediaPlayer!=null) {
seekBar.setMax(Assets.mediaPlayer.getDuration());
}
OnionFragment is loaded into MainActivity right away and looks like this
P.S. If anyone has extra time, how do i change size of seekbar ball and color
Change
seekBar.setMax(Assets.mediaPlayer.getDuration());
to
seekBar.setMax(Assets.mediaPlayer.getDuration()/1000);
- I am working on a project which needs to play video in slow motion.
- I am well aware that Android doesn't provide these functionality.
- I found PVPlayer Engine and libVLC which possessed these capabilities, but i didn't found any tutorial or proper documentation of including them in the android project and using them.
- So i tried doing this by using Runnable and Handler, it was successful in slowing down the video but they possessed jerks during playing.
public class MainActivity extends Activity {
VideoView vx;
Button mbutt;
Handler h ;
int curr = 0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
h = new Handler();
vx = (VideoView)findViewById(R.id.videoView);
mbutt = (Button)findViewById(R.id.button_Play);
vx.setVideoPath("/mnt/sdcard/you.mp4");
mbutt.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
vx.start();
}
});
Runnable r = new Runnable() {
#Override
public void run() {
if (vx != null) {
if (vx.isPlaying()){
vx.pause();
}
else{
vx.start();
}
}
h.postDelayed(this, 50);
}
};
h.postDelayed(r, 200);
}
}
- I have tried various combination of pause time and playing time to remove the jerks but all in vain, can anyone help me in removing these jerks so it plays a nice slow motion video or suggest another easy to integrate library into my android project.
Thanks in advance......
I am late but I found a solution for API 23 and above. Android 6.0 added PlaybackParams class to control playback behavior. -
Use setPlaybackParams method of MediaPlayer as given below -
videoview = (VideoView)findViewById(R.id.videoview);
videoview.setVideoURI("Your Video URI");
videoview.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
#Override
public void onPrepared(MediaPlayer mp) {
//works only from api 23
PlaybackParams myPlayBackParams = new PlaybackParams();
myPlayBackParams.setSpeed(0.5f); //here set speed eg. 0.5 for slow 2 for fast mode
mp.setPlaybackParams(myPlayBackParams);
videoview.start();//start your video.
}
});
If you are searching how to embedded VLC into android, you can ref to this.
and you can change the speed by calling setRate(0.5f) to libVLC for slow motion.
I am trying to program a sound app where on button click I would like to play 3-4 sound files one after the other. How can this be done? Also I would like to increase the time of play of the files. Is this where the loop variable in play function used??
Here is part of code I have done with SoundPool. However the files all play simultaneously :(
public void onClick(View v){
//check for scan button
if(v.getId()==R.id.playbutton){
queueSound(1000);
sound.changePitchfile3(R.raw.a0);
queueSound(5000);
sound.changePitchfile2(R.raw.a1);
queueSound(10000);
sound.changePitchfile3(R.raw.a3);
}
}
});
}
private void queueSound(final long delay) {
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
}}, delay);
public void changePitchfile1(int res1) {
int soundId = (Integer) mSounds.get(res1);
this.myPlayer.play(soundId, 1, 1, 0, 0,1.125f);
}
i have a button that plays a sound.
When i push the button multiple times, it plays the sound multiple times.
This is oke, i want this.
But when i click the stop button it must stop all sounds currently playing.
I used:
while (mediaPlayer.isPlaying()){mediaPlayer.stop();}
but it is not working, the sounds keep on playing. Can someone help me?
Here is my full code:
public class HelloSoundboard extends Activity {
MediaPlayer mediaPlayer;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button item1 = (Button)findViewById(R.id.item1);
item1.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
mediaPlayer = MediaPlayer.create(getBaseContext(), R.raw.atyourservice);
mediaPlayer.start();
}
});
Button stop = (Button)findViewById(R.id.stop);
stop.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
while (mediaPlayer.isPlaying()){mediaPlayer.stop();}
// mediaPlayer.stop();
}
});
}
}
SoundPool is a much better alternative for this purpose. I would caution strongly against instantiating multiple MediaPlayer instances as most systems do not have the resources to generate many parallel active instances. You wil find on many device that hitting the button upwards of 5 times will cause a memory based crash.
As far as stopping all active streams, there is not baked-in function for this, but it's easy to accomplish in a manner to similar to your existing code. As a side note, there is an autoPause() method, which halts all streams, but it doesn't truly end their playback (as the method name insinuates). Here is a simple example to manage your audio streams:
//SoundPool initialization somewhere
SoundPool pool = new SoundPool(10, AudioManager.STREAM_MUSIC, 0);
//Load your sound effect into the pool
int soundId = pool.load(...); //There are several versions of this, pick which fits your sound
List<Integer> streams = new ArrayList<Integer>();
Button item1 = (Button)findViewById(R.id.item1);
item1.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
int streamId = pool.play(soundId, 1.0f, 1.0f, 1, 0, 1.0f);
streams.add(streamId);
}
});
Button stop = (Button)findViewById(R.id.stop);
stop.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
for (Integer stream : streams) {
pool.stop(stream);
}
streams.clear();
}
});
It is much more memory efficient to manage a list of streamID values than MediaPlayer instances, and your users will thank you. Also, note that it is safe to call SoundPool.stop() even if the streamID is no longer valid, so you don't need to check for existing playback.
HTH
I'm not sure about this, but I think is better if you use a SoundPool.
"SoundPool is designed for short clips which can be kept in memory decompressed for quick access, this is best suited for sound effects in apps or games".
"MediaPlayer is designed for longer sound files or streams, this is best suited for music files or larger files".
You can use a list of MediaPlayers:
List<MediaPlayer> mps = new ArrayList<MediaPlayer>();
Button item1 = (Button)findViewById(R.id.item1);
item1.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
MediaPlayer mp = MediaPlayer.create(getBaseContext(), R.raw.atyourservice);
mp.start();
mps.add(mp);
}
});
Button stop = (Button)findViewById(R.id.stop);
stop.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
for (int i = mps.size() - 1; i >= 0; --i) { //changed ++i to --i
if (mps.get(i).isPlaying()) {
mps.get(i).stop();
}
mps.remove(i);
}
}
});
How do I set up an audiofile to play when a user touches an image.
Where should I store the audio file and what code should I use to actually play the file?
I don't want to bring up the MediaPlayer interface or anything like that.
I was thinking of doing it like this:
foo = (ImageView)this.findViewById(R.id.foo);
foo.setOnClickListener(this);
public void onClick(View v) {
if (foo.isTouched()) {
playAudioFile();
}
}
Thanks
This won't create a bring up the MediaPlayer interface... it will just play the sound you want.
Button boton = (Button) findViewById(R.id.boton);
boton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
MediaPlayer mp = MediaPlayer.create(TestSonido.this, R.raw.slayer);
mp.start();
}
});
In this case, R.raw.slayer represents an audio file called slayer.mp3 that is stored in the res/raw/ folder and once you click the button the droid will rock you...
You can also achieve the same using SoundPool.
MediaPlayer first loads the whole sound data in memory then play, so it produces some lag when we switch among sounds frequently.
SoundPool is a better option with small size sound file and produces better result with .ogg media file.
SoundPool pl = new SoundPool(5, AudioManager.STREAM_MUSIC, 0);
// 5 indicates the maximum number of simultaneous streams for this SoundPool object
pl.setOnLoadCompleteListener(new OnLoadCompleteListener() {
#Override
public void onLoadComplete(SoundPool soundPool, int sampleId, int status) {
// The onLoadComplet method is called when a sound has completed loading.
soundPool.play(sampleId, 1f, 1f, 0, 0, 1);
// second and third parameters indicates left and right value (range = 0.0 to 1.0)
}
});
Button btn = findViewById(R.id.boton);
btn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int sound = pl.load(this, R.raw.sound_01, 0);
}
});
public void aud_play(View view) {
if (!mp.isPlaying()) { //If media player is not playing it.
mp = MediaPlayer.create(this, R.raw.audio_name);
mp.start();
} else {// Toast of Already playing ...
}
}