This has been really confusing me of late. I have a thread which I start, and it keeps running until I kill it - by setting it's while variable to false.
This all works ok, but I lose contact with the thread on orientation change so I can't stop it. I've written this to try and clarify the problem:
public class FTTest extends Activity {
boolean isPlaying = false;
Player player = new Player();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fttest);
}
public void play(View v) {
if (!isPlaying) {
Log.d("Play Button", "Start Pressed");
player.start();
isPlaying = true;
} else {
Log.d("Play Button", "Stop Pressed");
player.going=false;
isPlaying = false;
}
}
}
The player is just this:
public class Player extends Thread{
int i;
public boolean going=true;
public void run(){
while(going){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
i++;
Log.d("Thread", "Running "+i+" times");
}
}
}
I've been reading about using fragments to solve this - but I just can't get my head around how to use them in this context. Is there something simple I'm missing?
Thanks for your help,
Mike
The idea is that you allow a fragment to be retained (fragment is not destroyed.. it will be persisted across your orientation changes). Your activity will get recreated and will be reattached to your Fragment which is still running your thread..
public class FTTest extends Activity {
private FTFragment fragment;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
fragment = new FTFragment();
getFragmentManager().beginTransaction().add(android.R.id.content, fragment).commit();
}
else {
fragment = (FTFragment)getFragmentManager().findFragmentById(android.R.id.content);
}
}
}
Retained fragment class
public class FTFragment extends Fragment
{
boolean isPlaying = false;
Player player = new Player();
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
public void play(View v) {
if (!isPlaying) {
Log.d("Play Button", "Start Pressed");
player.start();
isPlaying = true;
} else {
Log.d("Play Button", "Stop Pressed");
player.going=false;
isPlaying = false;
}
}
}
In your activity where you called play(View) you now should call fragment.play(View);
I think what is happening is that on your orientation change, the activity is getting recreated (as it usually does in android). When this happens, the Player object is being re-initialised as well.
So, you are actually going to have a reference to a new Player object and the old one will keep on running happily outside your control.
There are many ways to handle this. This blog post will be of help:
http://blog.andresteingress.com/2011/09/27/fighting-orientation-changes-in-android/
Related
I am trying to play with progress bars. I have this (below) simple activity which runs a progress bar N times one after the other, when I call Progress(N). It is working great but the problem I am facing is, if I press back button. I get into the mainActivity but the progress bars (the threads) are still running in background one after the other. As soon as they finish N loops, the intent is called and whatever I would be doing would be interrupted by this LOOP_OVER activity.
I tried solving this by my own. I tried using variable of Thread class (before I was directly doing it). And tried to interrupt() it at onDestroy() or even just before the intent is called but its not helping. How should I go about it?
public class Loop extends Activity {
private ProgressBar progressBar;
private CircleProgress circleProgress;
private int progressStatus = 0;
private Handler handler = new Handler();
private TextView myView;
private int started = 0, doneLoop=0;
private Thread th;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_loop);
progressBar = (ProgressBar) findViewById(R.id.progressBar1);
circleProgress = (CircleProgress) findViewById(R.id.circle_progress);
myView = (TextView) findViewById(R.id.instruction);
progressBar.setScaleY(3f);
// Start long running operation in a background thread
Progress(3);
}
#Override
public void onDestroy() {
// Below, everything I am just
th.interrupt();
Loop.this.finish();
Thread.currentThread().interrupt();
super.onDestroy();
}
public void Progress(final int numberOfRuns){
// QueView.setText(Que);
if(numberOfRuns == 0){
th.interrupt();
Intent myIntent = new Intent(Loop.this, LOOP_OVER.class);
startActivity(myIntent);
super.onDestroy();
finish();
}
th = new Thread(new Runnable() {
public void run() {
genNextSet();
while (progressStatus < 100) {
progressStatus += 1;
// Update the progress bar and display the
//current value in the text view
handler.post(new Runnable() {
public void run() {
circleProgress.setProgress(progressStatus);
progressBar.setProgress(progressStatus);
textView.setText(progressStatus+"/"+progressBar.getMax());
}
});
try {
runOnUiThread(new Runnable() {
#Override
public void run() {
// TODO Auto-generated method stub
myView.setText(Que);
}
});
// Sleep for 200 milliseconds.
//Just to display the progress slowly
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
progressStatus = 0;
Progress(numberOfRuns - 1);
}
});
th.start();
}
private void genNextSet() {
// so some cool here!
}
}
You can think of a class variable that is shared among all threads.
Try to add something like this:
private Boolean LOOP = true;
then
while (progressStatus < 100 && LOOP) {
and
#Override
public void onBackPressed() {
LOOP = false
}
also
if(LOOP == true){
// call intent
}
finish();
Your activity does not get destroyed, if you press the "Back"-key, thus onDestroy() will not be called.I'd override onBackPressed(), if I where you.Alternatively, you could try to put it into the onPause()-method.
You haven't override the back button pressed..try this
#Override
public void onBackPressed() {
th.interrupt();
Loop.this.finish();
Thread.currentThread().interrupt();
super.onBackPressed();
// add finish() if you want to kill current activity
}
I made a simple class that handles everything related to sound. Has an add, play, stop, release and releaseAll. How it works is that you have to add a song and then call play passing the name of the song you added. Anytime you need to stop, just call the stop function and pass the song's name as parameter and it should stop. My issue is that it isn't stopping even though it goes through stop().
Sound class:
public class Sound
{
private Map<String, MediaPlayer> songs = new HashMap<String, MediaPlayer>();
private MediaPlayer currentlyPlayingSong;
public Sound() {}
public void Add(int songId, String songName, Context context)
{
MediaPlayer song = MediaPlayer.create(context, songId);
songs.put(songName, song);
}
public void Play(String name, boolean shouldLoop)
{
MediaPlayer songToPlay = songs.get(name);
if ( songToPlay != currentlyPlayingSong && songToPlay != null)
{
currentlyPlayingSong = songToPlay;
currentlyPlayingSong.start();
currentlyPlayingSong.setLooping(shouldLoop);
}
}
public void Stop(String name)
{
MediaPlayer songToStop = songs.get(name);
if (songToStop != null)
{
songToStop.setLooping(false);
songToStop.stop();
}
}
public void Release(String name)
{
songs.get(name).release();
}
public void ReleaseAll()
{
LinkedList<MediaPlayer> _songs;
_songs = (LinkedList)songs.values();
for (int i = 0; i < _songs.size(); i++)
{
_songs.get(i).release();
}
}
}
On the activity's OnCreate I call Add then Play. Everything is fine until I try to call Stop from a fragment. Runs without any errors or exceptions, it simply doesn't stop.
Activity:
public class Main extends ActionBarActivity
{
private Sound sound = new Sound();
private static boolean isSoundOn = true;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
isSoundOn = true;
sound.Add(R.raw.drajamainmenueddited, "mainMenuSong", this);
//endregion
//Hide upper action bar
getSupportActionBar().hide();
if (isSoundOn)
sound.Play("mainMenuSong", true);
}
public void SetIsSoundOn(Boolean isOn)
{
isSoundOn = isOn;
}
public boolean GetIsSoundOn()
{
return isSoundOn;
}
public Sound GetSoundObj()
{
return sound;
}
}
Fragment:
public class MainMenuFragment extends Fragment {
private ImageButton soundImgBtn;
private FragmentConfig fragmentConfig;
public MainMenuFragment()
{
fragmentConfig = new FragmentConfig();
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
//region Initiators
View view = inflater.inflate(R.layout.fragment_main_menu, container, false);
soundImgBtn = (ImageButton)view.findViewById(R.id.soundImgBtn);
//endregion
//region Listeners
soundImgBtn.setOnClickListener(
new View.OnClickListener()
{
#Override
public void onClick(View v)
{
SoundImgBtnClick(v);
}
}
);
//endregion
//Changes audio img
if (((Main)getActivity()).GetIsSoundOn())
soundImgBtn.setImageResource(android.R.drawable.ic_lock_silent_mode_off);
else
soundImgBtn.setImageResource(android.R.drawable.ic_lock_silent_mode);
// Inflate the layout for this fragment
return view;
}
private void SoundImgBtnClick(View v)
{
//if sound is on and clicked, turn off
if (((Main)getActivity()).GetIsSoundOn())
{
((Main)getActivity()).SetIsSoundOn(false);
((Main)getActivity()).GetSoundObj().Stop("mainMenuSong");
soundImgBtn.setImageResource(android.R.drawable.ic_lock_silent_mode);
}
else
{
((Main)getActivity()).SetIsSoundOn(true);
((Main)getActivity()).GetSoundObj().Play("mainMenuSong", true);
soundImgBtn.setImageResource(android.R.drawable.ic_lock_silent_mode_off);
}
}
}
What I'm trying to do is emulate a mute button. Once clicked all sounds should be muted.
This is pretty much all I've coded, so far.
Cheers.
I suspect you're using different instances of MediaPlayer. You are allowed to do that BUT you must stop the song within the same instance.
About the code in Add():
MediaPlayer song = MediaPlayer.create(context, songId);
In Stop():
MediaPlayer songToStop = songs.get(name)
Note:
The above codes tell me you're using different instances of the MediaPlayer for one same song. The object song needs to be declared on a higher scope for you to access it and to stop the song.
Need to call release() method after stop() to free up resources.
try songToStop.release() instead
Got it to stop. My class had to be able to handle one song at a time and many fx at the same time. This is what I came up with.
Sound:
public class Sound
{
private static MediaPlayer currentlyPlayingSong,
currentlyPlayingFX;
public Sound() {}
public void PlayFX(int fxId, Context context, boolean shouldLoop)
{
MediaPlayer fx = MediaPlayer.create(context, fxId);
if (currentlyPlayingFX != fx)
{
StopFX();
currentlyPlayingFX = fx;
currentlyPlayingFX.start();
currentlyPlayingFX.setLooping(shouldLoop);
}
}
public void PlaySong(int songId, boolean shouldLoop, Context context)
{
MediaPlayer song = MediaPlayer.create(context, songId);
if (currentlyPlayingSong != song)
{
StopSong();
currentlyPlayingSong = song;
currentlyPlayingSong.start();
currentlyPlayingSong.setLooping(shouldLoop);
}
}
public void StopFX()
{
if (currentlyPlayingFX != null)
{
currentlyPlayingFX.stop();
currentlyPlayingFX.release();
currentlyPlayingFX = null;
}
}
public void StopSong()
{
if (currentlyPlayingSong != null)
{
currentlyPlayingSong.stop();
currentlyPlayingSong.release();
currentlyPlayingSong = null;
}
}
}
This is was based of what #The Original Android answered. Keep it on a single instance.
Thanks for the help.
here's my code for a very basic android sound player, on button press I expect a sound to play, or an IOException to be caught - seems simple enough. Instead I get "QCMediaPlayer mediaplayer NOT present", so how am I meant to play my track if there's no player available - & what should I do as an alternative?
I understand it's already been raised, but no trivial solution was given.
public class MainActivity extends Activity {
private MediaPlayer player;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button motivate = (Button) this.findViewById(R.id.motivate);
motivate.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) { // On click randomly select a sound then play it
try {
play();
}
catch (IOException e) {
e.printStackTrace();
}
}
});
}
public void stop() {
if (player != null) {
player.release();
player = null;
}
}
public void play() throws IOException {
stop();
int[] sound = {R.raw.a, R.raw.b, R.raw.c, R.raw.d, R.raw.e, R.raw.f, R.raw.g, R.raw.h, R.raw.i, R.raw.j, R.raw.k, R.raw.l, R.raw.m, R.raw.n, R.raw.o};
player = MediaPlayer.create(MainActivity.this, sound[0]);
player.setAudioStreamType(AudioManager.STREAM_MUSIC);
player.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
#Override
public void onCompletion(MediaPlayer player) {
stop();
}
});
}
}
You can try this one:https://bitbucket.org/edwardcw/libvlc-android-sample
We used it for stream playback
Okay so the answer to this was actually rather simple. MediaPlayer is an import that only 8/10 will work depending on your flavour of android and kernel. A constant is SoundPool; this will load and play a sound as required.
I was creating a splash screen in android using Thread.sleep(). (I know the another method - using handler, but I have to use this method for now.)
My code is as follows:
public class SplashScreen extends Activity {
Thread t;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_spash_screen);
new myclass();
}
class myclass implements Runnable{
myclass()
{
t = new Thread();
t.start();
}
public void run()
{
try{
Thread.sleep(1000);
Intent i = new Intent(getApplicationContext(), MainActivity.class);
startActivity(i);
finish();
}
catch(InterruptedException e){
System.out.println("thread interrupted");
}
}
}
}
It does not show any error, but splash screen stuck to the screen.
After 1s, it did not start another intent.
If you know the mistake then please help me.
run method of runnable is not called because you are not passing runnable to Thread constructor. so pass it as:
t = new Thread(this);
Try this,I always use this code in my Splash Activities.
public class SplashScreen extends Activity
{
private Thread mSplashThread;
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.splash_screen);
mSplashThread = new Thread()
{
#Override
public void run()
{
try
{
synchronized (this)
{
wait(2000);
}
}
catch(InterruptedException ex) {
ex.printStackTrace();
}
finish();
startActivity(new Intent(getApplicationContext(),MainActivity.class));
}
};
mSplashThread.start();
}
#Override
public boolean onTouchEvent(MotionEvent evt)
{
if (evt.getAction() == MotionEvent.ACTION_DOWN)
{
synchronized (mSplashThread)
{
mSplashThread.notifyAll();
}
}
return true;
}
}
I am creating a sound board and after clicking about 30 different sounds it stops working; I believe android is running out of memory. Below is my code. How can I implement .release() so that when the sound is done playing it is released? I don't really care if two things play at the same time; the clips are t0o short for this to be possible. I would just like to get my code set.
public class soundPageOne extends Activity {
public void onCreate(Bundle savedState) {
super.onCreate(savedState);
setContentView(R.layout.soundsone);
final MediaPlayer pg1 = MediaPlayer.create(this, R.raw.peter1);
Button playSound1 = (Button) this.findViewById(R.id.peter1Button);
playSound1.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
pg1.start();
}
});
I have done a lot of searching around but due to my lack of java/android knowledge I have not been able to get anything to work. Thanks in advance, let me know if anyone needs anymore code.
I left a comment, but I'll post an answer to show what I mean anyway...
The idea is that you have a set number of MediaPlayer instances that you can use. That way you never exceed the maximum number of instances. The array should be the length of the number of concurrent sounds you expect to be able to hear. If the sounds are local files, the length of time it takes to prepare the sounds should be almost negligible, so calling create inside the click handler should not result in terrible performance. Each of your buttons is associated with a particular resource, I suppose, so I set up a helper method to create and play the sounds for each button in the same way.
public class soundPageOne extends Activity {
private MediaPlayer[] mPlayers = new MediaPlayer[2];
private int mNextPlayer = 0;
#Override
public void onCreate(Bundle savedState) {
super.onCreate(savedState);
setContentView(R.layout.soundsone);
Button playSound1 = (Button)this.findViewById(R.id.peter1Button);
playSound1.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
startSound(R.raw.peter1);
}
});
}
public void onDestroy() {
super.onDestroy(); // <---------------------- This needed to be there
for (int i = 0; i < mPlayers.length; ++i)
if (mPlayers[i] != null)
try {
mPlayers[i].release();
mPlayers[i] = null;
}
catch (Exception ex) {
// handle...
}
}
private void startSound(int id) {
try {
if (mPlayers[mNextPlayer] != null) {
mPlayers[mNextPlayer].release();
mPlayers[mNextPlayer] = null;
}
mPlayers[mNextPlayer] = MediaPlayer.create(this, id);
mPlayers[mNextPlayer].start();
}
catch (Exception ex) {
// handle
}
finally {
++mNextPlayer;
mNextPlayer %= mPlayers.length;
}
}
}
Create a class, say AudioPlayer with a SoundPool variable. Setup a constructor to initialise the AudioPlayer object and create a Play method. SoundPool works better for short sounds played many times and does not require you to release.
public class AudioPlayer {
private SoundPool sPool = new SoundPool(Integer.MAX_VALUE, AudioManager.STREAM_MUSIC, 0);
public AudioPlayer(Context c, int id){
sounds.put("1",sPool.load(c, id, 1));
}
public void play(Context c) {
sPool.play("1", 1, 1, 1, 0, 1f);
}
}
So your class should look like
public class soundPageOne extends Activity {
public void onCreate(Bundle savedState) {
super.onCreate(savedState);
setContentView(R.layout.soundsone);
final AudioPlayer ap = new AudioPlayer(this, R.raw.sound);
Button playSound1 = (Button) this.findViewById(R.id.peter1Button);
playSound1.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
ap.play();
}
});
Could you use a MediaPlayer.OnCompletionListener?
Something like:
public class soundPageOne extends Activity implements MediaPlayer.OnCompletionListener {
public void onCreate(Bundle savedState) {
super.onCreate(savedState);
setContentView(R.layout.soundsone);
final MediaPlayer pg1 = MediaPlayer.create(this, R.raw.peter1);
//***set the listener here***
pg1.setOnCompletionListener(this);
Button playSound1 = (Button) this.findViewById(R.id.peter1Button);
playSound1.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
pg1.start();
}
});
}
//***this code will be executed once the sound finishes playing***
#Override
public void onCompletion(MediaPlayer mp) {
//log messages, other things can go here
mp.release();
}
Try something like this
Your activity class:
public class soundPageOne extends Activity {
public void onCreate(Bundle savedState) {
super.onCreate(savedState);
setContentView(R.layout.soundsone);
final AudioPlayer pg1 = new AudioPlayer();
Button playSound1 = (Button) this.findViewById(R.id.peter1Button);
playSound1.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
pg1.play(this, R.raw.sound);
}
});
}
This is another Java Class:
public class AudioPlayer {
private MediaPlayer mPlayer;
public void stop() {
if (mPlayer != null) {
mPlayer.release();
mPlayer = null;
}
}
public void play(Context c, int sound) {
stop();
mPlayer = MediaPlayer.create(c, sound);
mPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
public void onCompletion(MediaPlayer mp) {
stop();
}
});
mPlayer.start();
}
public boolean isPlaying() {
return mPlayer != null;
}
}