I have this RecyclerView that is a list of of instrumental beats, each item containing an Instrumental Name, Producer Name, and Audio Source that is retrieved from Firebase Storage. When you click on the play button, it triggers the OnClickListener and sets the data source to the position of the item's music link and plays the audio with OnPrepareListener. The problem I'm having is that if I click a different item's play button, it plays that audio as well, when my objective is to stop the previous audio and start the new audio. Here's my code:
public void onBindViewHolder(#NonNull final TrackHolder holder, final int position) {
holder.mInstrumentalName.setText(tracks.get(position).getInstrumentalName());
holder.mProducer.setText(tracks.get(position).getProducer());
final MediaPlayer mediaPlayer = new MediaPlayer();
holder.mPlayButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
holder.mPlayButton.toggle();
try {
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(tracks.get(position).getMusicLink());
mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
#Override
public void onPrepared(MediaPlayer mediaPlayer) {
if (mediaPlayer.isPlaying()) {
Log.d("is_playing", "is_playing");
mediaPlayer.stop();
try {
mediaPlayer.setDataSource(tracks.get(position).getMusicLink());
} catch (IOException e) {
e.printStackTrace();
}
mediaPlayer.start();
} else {
mediaPlayer.start();
}
}
});
mediaPlayer.prepareAsync();
mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
#Override
public void onCompletion(MediaPlayer mediaPlayer) {
mediaPlayer.release();
Log.d("released", "released");
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
});
holder.setItemClickListener(new ItemClickListener() {
#Override
public void onItemClick(View v, int pos) {
Toast.makeText(context, Integer.toString(tracks.size()), Toast.LENGTH_SHORT).show();
}
});
}
The picture below shows the list of items, with the the yellow meaning that the music is playing, and black not playing:
Use a single global MediaPlayer rather than a player per view. Then when you click an item, stop the audio, set a new data source, and restart.
Using multiple MediaPlayers in general is a bad idea anyway, those things use a ton of memory. Your current code is pretty bad because of it. Creating a new one on every bind is a lot of wasted memory and can lead to slow scrolling.
Related
I want a play button in my app that when clicked plays the audio and changes to pause, and when the pause button is clicked the audio stops and the button changes to play again and so on. But the button is not working as expected. Please help. I'm adding my java code below.
public class surah extends AppCompatActivity {
MediaPlayer mp = new MediaPlayer();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_surah);
mp=MediaPlayer.create(surah.this, R.raw.surahkahf);
final ImageView audio = (ImageView)findViewById(R.id.btn);
audio.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(mp.isPlaying()){
mp.stop();
audio.setBackgroundResource(R.drawable.play);
try {
mp.prepare();
}catch (IllegalStateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
else {
mp.start();
audio.setImageResource(R.drawable.pause);
}
}
});
}
}
Use audio.setImageResource(R.drawable.play) instead of audio.setBackgroundResource(R.drawable.play). Use mp.pause() instead of mp.stop();
First, as aborocz mentions in comments, you probably intend to pause playback instead of stop it, so the method you want to use is pause(). In that case you would not need to prepare the MediaPlayer again, and it will start from the same place it was paused when playback is resumed.
Second, the isPlaying() method is not particularly appropriate for this purpose. There are race conditions that prevent the desired behavior. From the Android MediaPlayer documentation:
Note that the transition from the Started state to the Paused state
and vice versa happens asynchronously in the player engine. It may
take some time before the state is updated in calls to isPlaying(),
and it can be a number of seconds in the case of streamed content.
Instead, store your own boolean value.
public class surah extends AppCompatActivity {
private MediaPlayer mp;
private boolean isMediaPlaying = false;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_surah);
mp = MediaPlayer.create(surah.this, R.raw.surahkahf);
final ImageView audio = (ImageView)findViewById(R.id.btn);
audio.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (isMediaPlaying) {
mp.pause();
audio.setBackgroundResource(R.drawable.play);
isMediaPlaying = false;
} else {
mp.start();
audio.setImageResource(R.drawable.pause);
isMediaPlaying = true;
}
}
});
}
}
I am having a listview in my app. Each listview item has button which will play an audio from a url received from web service. But my problem is that if I click play button from the next item then both start playing together. I am having problem in this. I want only one to play at a time. Right now I am creating new Media player object everytime button is clicked, but I also tried creating a single global object but in this case it only plays first time and not after it. What is the possible solution of it.
finalHolder.iv_sound.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
try {
MediaPlayer mp = new MediaPlayer();
mp.setDataSource(birdsUrlList.get(position).getUrl_audio());
mp.prepare();
mp.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
#Override
public void onPrepared(MediaPlayer mp) {
mp.start();
}
});
mp.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
#Override
public void onCompletion(MediaPlayer mp) {
mp.release();
}
});
//startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(birdsUrlList.get(position).getUrl_video())));
} catch (Exception e) {
e.printStackTrace();
}
}
});
Make the mp variable global and remove this:
mp.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
#Override
public void onCompletion(MediaPlayer mp) {
mp.release();
}
});
because if you take a look at this reference http://developer.android.com/reference/android/media/MediaPlayer.html#StateDiagram it says:
Once the MediaPlayer object is in the End state, it can no longer be
used and there is no way to bring it back to any other state.
And when you call mp.release(); the media player WILL go to that state.
Then make your onClickListener look something like this:
#Override
public void onClick(View v) {
try {
if (mp.isPlaying()) {
mp.stop();
mp.reset();
}
mp.setDataSource(birdsUrlList.get(position).getUrl_audio());
//... and so on
mp.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
#Override
public void onCompletion(MediaPlayer mp) {
mp.stop();
mp.reset();
}
});
The trick there is to make it back to Idle state so you can set the new data source and start playing again. It's all about the states...
I'm testing Android sound implementation. I have an "imagenButton" and when I push it, a method "musica()" is called. Is really simple:
public void musica(View v) {
img = (ImageView) findViewById(R.id.Migrunido);
if(m.isPlaying()) {
m.stop();
}
else {
m.start();
}
}
It works, but want when I press the button, the sound repeats. I was reading a lot about threads and I have to prepare it before "stop()" but can't fix it.
I have to prepare my method? Or what is the problem?
Thanks
try
public static void playAudio(int id) {
mediaPlayer = MediaPlayer.create(context,id);
if (!mediaPlayer.isPlaying()) {
mediaPlayer.start();
mediaPlayer.setOnCompletionListener(new OnCompletionListener() {
#Override
public void onCompletion(MediaPlayer mp) {
mp.stop();
mp.release();
}
});
}else{
mediaPlayer.stop();
mediaPlayer.release();
}
}
if this doesn't work try mediaPlayer.prepare(); before you start.
I have been having this issue for a while now.
I have a simple application that plays a playlist of videos within a videoview with a pause of a few seconds between them.
private final Runnable playNormalVideo = new Runnable() {
public void run() {
try {
final String filepath = ...;
viewVideo.setOnErrorListener(new OnErrorListener() {
public boolean onError(MediaPlayer mp, int what, int extra) {
onEndVideo(false);
return true;
}
});
viewVideo.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
#Override
public void onCompletion(MediaPlayer mp) {
onEndVideo(false);
}
});
viewVideo.setOnPreparedListener(new OnPreparedListener() {
public void onPrepared(MediaPlayer mp) {
background.setVisibility(View.INVISIBLE);
viewVideo.start();
}
});
viewVideo.setVideoPath(filepath);
viewVideo.setVisibility(View.VISIBLE);
viewVideo.requestFocus();
} catch (Exception e) {
String error = Utils.getStackTrace(e);
Utils.log(true, error);
}
}
};
and in my onEndVideo() function I make the background visible and check what video to play next and with a Handler i request to play it after x seconds.
My issue is the following :
Within a long run (approx 1 day) the background becomes invisible within a lot of seconds before the video starts. I don't understand why. If someone could help me get rid of this issue.
I also thought I should free memory between video plays but i don't seem to find how to do that.
Note : All the videos are saved on the device.
Thanks for any help.
This is the code in my main Activity:
private void initControls()
{
streamButton = (Button) findViewById(R.id.button_stream);
streamButton.setOnClickListener(new View.OnClickListener()
{
public void onClick(View view)
{
loading = (ProgressBar) findViewById(R.id.progressBar1);
Toast
.makeText(
MyActivity.this,
"The following stream is about to start" + station,
Toast.LENGTH_LONG).show();
player.playAudioFile(urlstring2);
streamButton.setEnabled(false);
notificate();
}
});
}
And here is the code in witch i start the mediaplayer itself:
public class player extends MyMainActivity{
static MediaPlayer player;
private static final String TAG = player.class.getSimpleName();
public static void playAudioFile(String urlstring2) {
player = new MediaPlayer();
try {
player.setDataSource(urlstring2);
loading.setVisibility(View.VISIBLE);
player.prepareAsync();
//player.start();
} catch (Throwable thr) {
Log.e(TAG, "could not play audio", thr);
loading.setVisibility(View.INVISIBLE);
}
player.setOnPreparedListener(new OnPreparedListener() {
#Override
public void onPrepared(MediaPlayer mp) {
player.start();
loading.setVisibility(View.INVISIBLE);
}
});
}
public static void releasePlayer() {
if (player != null) {
// Player-Ressourcen freigeben
player.release();
player = null;
}
}
}
What I want to do is to show the progressBar (the cicling one) when the user presses the "play" Button, and I need it to disapear when the mediaplayer starts to play.
atm I just get the circle to show after the player has startet to play and never disapear ...
Edit: I got it to work.
I did initiate the player like this: player.prepare(); insteas of player.prepareAsync();
So the progressbar was shown for a view milliseconds.
Try this out.
Start the Progress Bar in the button click here like this,
streamButton.setOnClickListener(new View.OnClickListener()
{
public void onClick(View view)
{
ProgressDialog progressDialog=null;
progressDialog=ProgressDialog.show(context, "", "Playing...");
Toast
.makeText(
MyActivity.this,
"The following stream is about to start" + station,
Toast.LENGTH_LONG).show();
player.playAudioFile(urlstring2);
streamButton.setEnabled(false);
notificate();
}
});
And you have to override the media player's on Prepared listener, so that you can stop the progress bar within it like this,
Player.setOnPreparedListener(new OnPreparedListener() {
#Override
public void onPrepared(MediaPlayer mp) {
progressDialog.cancel();
}
});
By this you can achieve to show Progress until your Mediaplayer starts to play the file.
Came back to finally mark this Question as solved, so I copied what I edited into my question previously.
I got it to work. I did initiate the player like this:
player.prepare(); insteas of player.prepareAsync(); So the progressbar
was shown for a view milliseconds.
add the code to hide your ProgressBar after this line:
player.start();
And also in your catch block. That should make it go away once the player is either playing, or failing to play.
If you add the code that you are using to show the ProgressBar now I can help you with how to hide it also.