Playing a looping soundtrack - android

I am making a game and I want to have a list of songs play in the background and loop once it's gone though them all. To do this I loop through creating MediaPlayers and calling setNextMediaPlayer with next player in the list unless it's the last item; then I set it back to the first item in the list. This works but when it loops and tries to play the first song again it tells me that I called play() in an invalid state. So I added an OnCompleteListener so when a song ends I call prepare() on it again but that didn't help. What do I need to do to a MediaPlayer that has reached the end in order to get it to play again?
-= UPDATE =-
Here's my code for initializing the MediaPlayers (minus the try's and catches since that just clutters it up):
String[] assets = manager.list("songs");
Collections.shuffle(Arrays.asList(assets));
for(String name : assets)
{
MediaPlayer player = new MediaPlayer();
AssetFileDescriptor descriptor = manager.openFd("songs/"+name);
player.setAudioStreamType(AudioManager.STREAM_MUSIC);
player.setDataSource(descriptor.getFileDescriptor(), descriptor.getStartOffset(), descriptor.getLength());
player.setOnCompletionListener(_reset);
player.prepare();
if(_songs.size() > 0)
{
_songs.get(_songs.size() - 1).setNextMediaPlayer(player);
}
_songs.add(player);
}
_songs.get(_songs.size() - 1).setNextMediaPlayer(_songs.get(0));

If you are getting the same error after calling prepare inside onCompleteListener it might be because the next MediaPlayer, which is now not prepared, is invoked before onCompleteListener.
I would suggest a different approach that might solve your problem and be more efficient as well:
Maintain the order of songs in some data structure such as Circular Linked List so you give it the current song name and it give you the next one
Prepare and start playing the first song
Override onPrepared and get the next song name from your data structure then call setNextMediaPlayer with a newly created and prepared MediaPlayer object
I believe this approach is more efficient because you do not create many instances of MediaPlayer in advance. Also, you provide a prepared nextMediaPlayer every time when you start the current song.

Related

Android ExoPlayer PlayList Pause Current Track When It Finishes

My goal is to pause the Current track right after it finishes, but the default behavior of playlist playback will not pause until the whole Playlist is finished.
I've tried using onPositionDiscontinuity() but it is called after the track has changed to the next one.
override fun onPositionDiscontinuity(reason: Int) {
super.onPositionDiscontinuity(reason)
if (reason == SimpleExoPlayer.DISCONTINUITY_REASON_PERIOD_TRANSITION) {
Log.v("xxx", "called!") //not called at the end of current track
}
}
And it seems like not supported natively (by official):
https://github.com/google/ExoPlayer/issues/3773
You can use the setPauseAtEndOfMediaItems method available on SimpleExoplayer.Builder like so:
player = SimpleExoPlayer.Builder(context)
.setPauseAtEndOfMediaItems(true)
.yourOtherOptions()
.build()
Unfortunately, there is no direct callback available to notify the end of the last frame of the current track. The only thing available with the ConcatenatingMediaSource, to know the end of a track is onPositionDiscontinuity(), but as you know that would be dispatched only after the first frame of the next track is already rendered. So in that case I think we can have the below possibilities wrt your use case:
Use VideoFrameMetadataListener interface and override the onVideoFrameAboutToBeRendered(), which would be called on the playback thread when a video frame is about to be rendered. In your case just before the next track rendering. link
Get the track duration [getDuration()] and keep getting the current playback position using getCurrentPosition() in every second(or any other time interval). And pause the playback when it returns the specified time. You can use a CountDownTimer for this and in the timer callback, onTick(), invoke getCurrentPosition() for the current track. 
Use PlayerMessage to fire events at specified playback positions: The playback position at which it should be executed can be set using PlayerMessage.setPosition.link
Use onMediaItemTransition(): Called when playback transitions to another media item. Here generally we update the application’s UI for the new media item. So instead of updating the UI, we can pause the playback. Not sure if this gets called before or after onPositionDiscontinuity(). Feasibility needs to be verified.

exoplayer2: pause video at start with ConcatenatingMediaSource

While playing videos from a ConcatenatingMediaSource playlist, I would like the player to pause automatically at the start of a new item eg. not automatically playing it.
Using the demo application, I modified onPositionDiscontinuity function to detect current item change:
int currentPosition = 0;
#Override
public void onPositionDiscontinuity(#Player.DiscontinuityReason int reason) {
if (inErrorState) {
// This will only occur if the user has performed a seek whilst in the error state. Update
// the resume position so that if the user then retries, playback will resume from the
// position to which they seeked.
updateResumePosition();
}
if (player.getCurrentWindowIndex() != currentPosition) {
currentPosition = player.getCurrentWindowIndex();
player.setPlayWhenReady(false);
}
}
While this code pauses the player, it does not clear the surface view used by the player hence we are still seeing the last frame of the previous video. I suppose this callback is invoked too soon, but that's the only callback I found which was always invoked on playlist item change (onPlayerStateChanged might not be invoked).
How can I have the first frame of the newly current item displayed instead of the previous item last frame?
My weak workaround is to delay invocation of 200ms with Handler().postDelayed({ mPlayer?.playWhenReady = false }, 200).
This is not supported yet:
The problem with using the event listener is that there is no way to ensure the request to pause in your event listener
is handled while the first frame of the new playlist item is showing.
If stopping at approximately the right frame is fine, your solution of
pausing the player on position discontinuity looks fine, but I think
there's no guarantee that the video renderer will have kept up with
the player position which is why you still see a frame from the
previous source at the point of pausing the player. For what it's
worth, in my testing the video renderer did advance to the first frame
of the next playlist item before the request to pause was handled.
A couple of other suggestions:
You could try customizing your MediaSource to insert a position discontinuity at the start of each period. I think then you'd get
onRenderedFirstFrame at the start of each item. If you pause the
player there you can guarantee to pause the player at a frame in the
new playlist item.
To get this to work perfectly, in the sense that the first frame of the new playlist item is shown and the player pauses before any
other frame is shown, it will be necessary to coordinate showing a
frame and blocking further rendering (on the playback thread) with
pausing the player (on the thread your app is using to interact with
the payer). This will require a bit of code, but I think it is
probably possible roughly as follows: subclass MediaCodecVideoRenderer
and override onStreamChanged to get the time offset of the new
playlist item. Then override processOutputBuffer. In this method you
can detect when the first frame of the new stream has been rendered
(based on the time offset) and then prevent processing any output
frames until you've paused the player on your app's thread. After the
request to pause has been handled and the renderer is stopped
(onStopped) you can unblock processOutputBuffer.
We could also look at supporting this directly in the player, but it
will likely be a low priority at least for the moment.

How to stop all sounds in SoundPool?

I am using SoundPool to play sfx sounds in a game on Android. In most cases it works perfectly, except sometimes I need to stop all sounds at once not pause (doesn't matter if they are set to loop or not).
I can't figure out how to stop a sound from playing without knowing the StreamID of that sound. What I know:
soundpool.load(...some sound...) returns a soundID
soundpool.play(soundID) plays the sound and returns a streamID
soundpool.stop(streamID) stops the sound
My question is, how can I stop a sound without knowing the streamID ? I tried tracking all streamIDs in a list, but sometimes there are so many short streams playing at once, that it won't work. And I can't find any method in SoundPoolto get the active streamIDs. Does anyone know how to stop all sounds?
Any hint is appreciated! thanks
I'd recommend using autoPause() and autoResume() to pause and restart your sounds. This function is part of the soundpool:
http://developer.android.com/reference/android/media/SoundPool.html#autoPause()
What about using .release() ? Accourding to the documentation:
Release the SoundPool resources. Release all memory and native resources used by the SoundPool object. The SoundPool can no longer be used and the reference should be set to null.
I think it also stops everything. (Actually it was causing a bug at one of my apps, that's why I say it)
However, I believe that if you want to play any sounds later, you may have to load them again.
SoundPool doesn't have a method to retrieve the streamID from the index of the stream, like it should. So even when you know the maximum number of streams, you can't just iterate through like the following:
for(int index=0;index<MAXSTREAMS;index++){
int streamid = SoundPool.getStreamID(index);//getStreamID action doesn't exist :(
soundpool.stop(streamid);
}
As you mentioned, the coder has to manage this himself with a list. I implement the declaration as follows:
List<Integer> streams = new ArrayList<Integer>();
You can add and remove soundIDs to this list as you play and stop sounds as follows:
streams.add(batattack_soundID);
streams.remove(Integer.valueOf(batattack_soundID));//NOT streams.remove(batattack_soundID);
Be sure to use Integer.valueOf() or .remove() will interpret your parameter as an index position, likely giving you an IndexOutOfBoundsException.
To clean everything up you can code as follows:
for (Integer stream : streams) {
soundPool.stop(stream);
}
streams.clear();

Reload data source in android media player

I'm having a hard time with media player in android. Basically I try to play a song from my sd card which is downloading (some sort of streaming). So after the song was downloaded (20%) the song starts to play and if I leave it like this it works fine until the end. But if I try to seek at the end (over 20%) obviously it won't work because my seek position is over EOF file so I made this code inside a method
MediaPlayer mp = new MediaPlayer();
mp.setDataSource(sFilePath);
mp.prepare();
int offset = (progress * mp.getDuration()) / 100;
if (sCompleted) return;
sLastSeek = offset;
if (offset > sMediaPlayer.getDuration()) {
sMediaPlayer.reset();
sMediaPlayer.setDataSource(sFilePath);
sMediaPlayer.prepare();
} else {
sMediaPlayer.seekTo(offset);
}
Where sMediaPlayer is my mediaPlayer, and sFilePath is the file path. In theory the case I presented should be covered but when I seek over the file length the player is reseted (as the code says) but is not playing and this is very awkward and is over my powers.
Maybe the mediaPlayer stopt because it reaches the end of the song. Either start it again, refresh the file in steady intervals (say every 5 secs), or just use onCompletionListener. In the last case, when the file ends, you can reload the file and continue playing... mp.getCurrentPosition() to get where it left off playing.
I also noticed you refer to sMediaPlayer, but your MediaPlayer is defined as mp.

Android Media Player - Getting the number of times a video played in SetLooping

I am Playing the video in loop using set Looping(true) option and i will stop the media player after a particular event happened. It is working fine. But i want to know the number of times that my video got played in the loop.
MediaPlayer doesn't provide that information, you need to make it loop yourself and count how many times it has restarted. To do this, something in your app will need to extend OnCompletionListener and do something like
int count = 0;
public void onCompletion(MediaPlayer mediaPlayer) {
count++;
mediaplayer.seekTo(0);
mediaplayer.start();
}
And you need to set mediaPlayer.setLooping(false)

Categories

Resources