I just stumbled across problem with it's this not-so-obvious solution and thought it might be helpful to some if I share my findings.
There are lots of similar questions but they didn't help me solve mine. (are different)
I have a activity that plays a video with sound, then displays an image for 3 seconds, then displays an animated mapview for 10 seconds and then plays another video.
video1 (~60sec)
image (~3sec)
animated mapview (~10sec)
video2 (~30sec)
the arose when trying to play audio from the moment the first video finished.
the audio played fine for the duration of the mapview, then stopped.
I tried all sorts of scenarios but the music always stopped when something changed, although some combinations worked (e.g. without map and video2 (just blackscreen instead)).
I did take a very close look at all the logs but didnt find anything helpful.
The issue is not that the mediaplayer is being gc'd or finalized somehow before finishing.
private static MediaPlayer mediaplayer;
and in OnCreate:
mediaplayer = new MediaPlayer();
AudioFocusRequest.Builder mp;
mediaplayer.setAudioAttributes(
new AudioAttributes
.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build());
try {
mediaplayer.setDataSource(getApplicationContext(), Uri.parse("android.resource://" + getPackageName() + "/" + R.raw.audio));
} catch (IOException e) {
e.printStackTrace();
Log.e("audio_management","error trying to set data source for audio");
}
try {
mediaplayer.prepare();
} catch (IOException e) {
e.printStackTrace();
Log.e("audio_management","error preparing");
}
Here's an example of how I play the videos
z.schedule(new TimerTask() {
#Override
public void run() {
runOnUiThread(new Runnable() {
#Override
public void run() {
playPart2();
}
});
}
}, PARTONELENGTH + MAPDISPLAYTIME + IMAGEDISPLAYTIME);
and this is the playPart2() function:
public void playPart2() {
ImageView imgbg = findViewById(R.id.imageView);
ImageView agO = findViewById(R.id.agent1);
ImageView agTw = findViewById(R.id.agent2);
ImageView agTh = findViewById(R.id.agent3);
ImageView agFo = findViewById(R.id.agent4);
ImageView agFi = findViewById(R.id.agent5);
ImageView agSi = findViewById(R.id.agent6);
VideoView player = findViewById(R.id.videoView);
MapView mMap = findViewById(R.id.map);
mMap.setVisibility(View.INVISIBLE);
imgbg.setVisibility(View.INVISIBLE);
agO.setVisibility(View.INVISIBLE);
agTw.setVisibility(View.INVISIBLE);
agTh.setVisibility(View.INVISIBLE);
agFo.setVisibility(View.INVISIBLE);
agFi.setVisibility(View.INVISIBLE);
agSi.setVisibility(View.INVISIBLE);
player.setVisibility(View.VISIBLE);
Uri uri = Uri.parse("android.resource://" + getPackageName() + "/" + R.raw.part2);
player.setVideoURI(uri);
player.start();
}
I really dont know what is going on - I 've read through all the documentation concerning MediaPlayer and VideoViews. It says that it is possible to output 2 streams of audio at the same time. But it doesnt work.
turns out the videoView starting playback somehow paused the mediaplayer.
what solved the problem was just calling mediaplayer.start() again. it did resume where it had stopped all the times before. the (apparently existing) short break in playback is not noticeable.
why is this behaviour of mediaplayer/videoview not mentioned anywhere?
issue-solving-code:
z.schedule(new TimerTask() {
#Override
public void run() {
runOnUiThread(new Runnable() {
#Override
public void run() {
playPart2();
mediaplayer.start();
}
});
}
}, PARTONELENGTH + MAPDISPLAYTIME + IMAGEDISPLAYTIME);
Related
I have a custom view (PieView) that is has a rotating animation. Now I would like to play tick tick tick tick... sound synchronously with the rotation speed (that is, when the rotation speed is fast, the tick tick should be fast, when rotation is slow, the tick tick should be slow).
To do this, first I created an mp3 file named magicbox_tick.mp3 that has only ONE (1) tick. Next I tried to play the sound with Animation.setUpdateListener().
First I tried to play music with MediaPlayer but after some 10 or 15 ticks, it stoped. So now I am trying SoundPool to play the music.
The relevant code segment looks like this:
public PieView extends View {
// ... constructors, other methods etc
private SoundPool soundPool;
private int soundId;
void init(){ // called inside those constructors
SoundPool soundPool = new SoundPool(5, AudioManager.STREAM_MUSIC, 0);
soundId = soundPool.load(getContext(), R.raw.magicbox_tick, 1);
}
public void rotateTo(){
animate()..setInterpolator(new DecelerateInterpolator())
.setDuration(mDuration)
.setListener(someListener)
.rotation(targetAngle)
.setUpdateListener(animation -> {
myPlaySound(); // <----------------------- This is the sound playing code
})
.start();
}
void myPlaySound(){
soundPool.play(soundId, 1, 1, 0, 0, 1); // this doesnot play the `tick` sound
// previously I used MediaPlayer like this:
/*
MediaPlayer mp = new MediaPlayer.create(getContext(), R.raw.magicbox_tick);
mp.play();
// these 2 line, after some 10 ticks, stopped working.
*/
}
}
I have never done anything like this, and I don't know how to fix this. Can anyone help me?
Please note that I am open to all answers as long as it works. You don't have to use SoundPool. So suppose if you can make it work with android MediaPlayer, I am ok with that.
Special thanks to Mr Mike M for his valuable comment. I was able to fix it using MediaPlayer. MediaPlayer.release() method should be called. And to make the sound synced with the angular motion, I kept an if block that checks if the rotation dTheta is greater than tolerance angle.
So, if anyone needs it, the complete code looks like this:
public PieView extends View{
private float omega0; // holds the previous rotation
/**
* #brief: plays a music using mediaPlayer
* #input:
* #output: void, plays a music
* */
private void myPlayTick() {
float omega1 = Math.abs(getRotation());
float dOmeda = 0;
if(omega1>omega0){
dOmeda = omega1 - omega0;
}else{
dOmeda = omega0-omega1;
}
if(dOmeda > threshold){
omega0 = omega1; // update previous rotation
final MediaPlayer mp = MediaPlayer.create(getContext(), R.raw.magicbox_tick);
mp.start();
mp.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
#Override
public void onCompletion(MediaPlayer mp) {
releaseMediaPlayer(mp);
}
});
}
}
/**
* #brief: releases mediaPlayer resource so that other mediaPlayers can use sound hardware resources
* #input: MediaPlayer object
* #output: void
* */
private void releaseMediaPlayer(MediaPlayer mediaPlayer) {
try {
if (mediaPlayer != null) {
if (mediaPlayer.isPlaying())
mediaPlayer.stop();
mediaPlayer.release();
mediaPlayer = null;
}
} catch (Exception e) {
e.printStackTrace();
}
}
public void rotateTo(){
animate()..setInterpolator(new DecelerateInterpolator())
.setDuration(mDuration)
.setListener(someListener)
.rotation(targetAngle)
.setUpdateListener(animation -> {
myPlayTick();
})
.start();
}
// ... rest of the code, such as constructors, etc
}
I have a seekbar in my fragment that I use to show the progress of a MediaPlayertrack that is playing. Everything works as expected until I change orientation of the device. I save my fragment instance and load it back, and all of the other views are repopulated as they should be, and the music keeps right on playing as it should, but the seekbar goes right to the max, as if the track has reached it's max duration. The runnable that is updating the seekbar based on the mediaplayer's position keeps running after the orientation change as well, and logging the currentPos value in the runnable shows that it stays in sync with the mMediaPlayer.getCurrentPosition() as it should, even after the orientation change...So basically, it continues to feed the correct position to the seekbar; the seekbar just isn't updating after the orientation change. It makes me wonder if the seekbar instance stays the same after the change. If it doesn't stay the same instance, I can't figure out why or how to fix it.
The onCLick method of my play button calls the following to start the track and hook up the seekbar to it:
private void startPlayer(String url){
try {
mMediaPlayer.setDataSource(url);
} catch (IOException e) {
e.printStackTrace();
}
mMediaPlayer.prepareAsync();
mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
#Override
public void onPrepared(MediaPlayer mp) {
mMediaPlayer.start();
playerState = PlayerState.PLAYING;
setButtonsForPlayerState();
setSeekBarValues();
mHandler = new Handler();
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
if (mMediaPlayer != null ) {
int currentPos = mMediaPlayer.getCurrentPosition();
Log.e(LOG_TAG, "Current pos is " + currentPos);
seekBar.setProgress(currentPos);
if (currentPos<10000){
seekBarStartTime.setText("0:0" + currentPos / 1000);
}else{
seekBarStartTime.setText("0:" + currentPos / 1000);
}
}
if (mMediaPlayer.isPlaying()){
mHandler.postDelayed(this, 50);
}
}
}
);
}
});
}
and here is the setSeekBarValues() method that is called in the above method:
private void setSeekBarValues() {
seekBar.setMax(mMediaPlayer.getDuration());
seekBarFinishTime.setText("0:" + seekBar.getMax()/1000);
seekBar.setProgress(0);
}
seekBarStartTime and seekBarFinishTime are just TextViews that show the current position and end time of the track, respectivily.
Since all of my other views, streaming music, etc. seems to be working as it should on the re-creation of Activity and Fragment, I don't think it's an issue with saving instance state, but if you want me to post that code as well please let me know.
Any ideas are appreciated.
I'm playing around with some basic android, and I'm trying to write a metronome app. The basic idea is that I'm using a runnable in order to trigger a sound after a time period (msPeriod). I've tried to use SoundPool, but it will just log 'sample not loaded', and trying to ititialise a MediaPlayer causes the app to crash on opening. Could you explain to me where I'm going wrong please? Below is my code with MediaPlayer.
//I create the media player first thing inside MainActivity
private Handler handler = new Handler();
int msPeriod = 1000;
MediaPlayer mpHigh = MediaPlayer.create(this, R.raw.hightick);
MediaPlayer mpLow = MediaPlayer.create(this, R.raw.lowtick);
//within an onClick Listener
onClick(View v) { handler.postDelayed(startMetron, msPeriod); }
//the runnable that starts the metronome
private Runnable startMetron = new Runnable( ) {
#Override
public void run() {
if(isRunning){
if (count == 4) {
count = 1;
mphigh.start();
} else {
count++;
mplow.start();
}
}
textCount.setText(String.valueOf(count));
//triggering the next run
handler.postDelayed(this, msPeriod);
}
};
Thanks so much for bearing with me!
You are running a separate thread . The UI element must be updated form the main thread... so..
runOnUiThread(new Runnable(){
public void run() {
textCount.setText(String.valueOf(count));
}
});
This makes sure that the textview is update from the UI thread and your app will not crash.
I've tried to find a solution to seek frame by frame (not only to keyframes) in my android app.
approach: A simple VideoView of the android sdk:
Here I've got a onSeekCompleteListener of the base class MediaPlayer from the onPreparedListener of VideoView and pause the playback right after started it and moved to a preferred position.
Problem: Only seeks to keyframes!
approach: MediaMetaRetriever:
I have got something like this:
MediaMetadataRetriever retriever;
long Position;
ImageView video;
TextView lbPosition;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
video = (ImageView)findViewById(R.id.imVideo);
lbPosition = (TextView)findViewById(R.id.lbPosition);
bnTake = (Button)findViewById(R.id.bntake);
retriever.setDataSource(this, Uri.parse("android.resource://" + getPackageName() + "/" + R.raw.vid));
video.setOnClickListener(new ImageView.OnClickListener() {
#Override
public void onClick(View arg0) {
Position += 100000;
Bitmap frame = retriever.getFrameAtTime(Position, MediaMetadataRetriever.OPTION_CLOSEST);
video.setImageBitmap(frame);
lbPosition.setText(String.valueOf(Position));
}
});
bnTake.setOnClickListener(new Button.OnClickListener() {
#Override
public void onClick(View arg0) {
Intent takeVideoIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
startActivityForResult(takeVideoIntent, 123123);
}
});
}
When I use OPTION_CLOSESTS in retriever.getFrameAtTime it gives me a getFrameAtTime: videoFrame is a NULL pointer error. When I don't use this option, it goes through keyframes.
Maybe possible solutions:
approach: Record video with more keyframes. If I would record the video with an higher keyframe rate in my android app, could that be a solution? Is this possible?
approach (I haven't tried!): I've heard something of video player 3rd party libraries for android that use FFMPEG. Is there any library that can seek while in pause state and update the position for the video on the screen?
Have you tried FFmpegMediaMetadataRetriever?
I have an app which replaces an image and plays a sound on click of a button, What I am looking to do is revert back to the original image once the sound has stopped playing.
My button click listener:
public void onClick(View v) {
// Perform action on click
Context context = getApplicationContext();
CharSequence text = "Playing Theme";
int duration = Toast.LENGTH_SHORT;
//this is the replaced image while the sound is playing
imgSheep.setImageResource(R.drawable.replacedimage);
Toast.makeText(context, text, duration).show();
playSound(R.drawable.sound);
}
My Sound playing function:
//plays a sound file
private void playSound(int sFile) {
//set up MediaPlayer
final int medFile = sFile;
Thread thread = new Thread(new Runnable() {
public void run() {
playSound = MediaPlayer.create(getApplicationContext(), medFile);
playSound.start();
}
});
thread.start();
}
I know I can use a method like:
mp.setOnCompletionListener(new OnCompletionListener() {
#Override
public void onCompletion(MediaPlayer mp) {
performOnEnd();
}
});
So can I have it like this:
playSound.setOnCompletionListener(new OnCompletionListener() {
#Override
public void onCompletion(MediaPlayer playSound) {
imgSheep.setImageResource(R.drawable.originalimage);
}
});
Your problem is your thread. In playSound, you're starting a new thread which creates the media player and plays the sound. However you're setting the onCompletionListener in onCreate. There's a race condition there- if the new thread isn't schedules and doesn't run and set the mediaPlayer variable before you hit that line, you'll crash with a NullPointerError.
I suggest just losing the thread. MediaPlayer already will play in the background and not hang the UI thread.