I'm playing video via MediPlayer in my android application and have SeekBar displayed. Now I want this seeks bar to automatically update as the video progresses so it should automatically move from left to right. At the moment, (code below) the bar updates and this is done via running thread, that every second updates the progress of seekBar. The problem is it is not smooth and as seekBar is updated via its seekProgress() the video stops for split second and all is very jumpy. Now I would like it to have updated more often then every second as well as keep functionality that I already implemented to allow user to tap on the bar and change progress of the video.
I'm after something like Android MediaPLayer application have, seekBar is on transparent background and all is smooth and I have no idea how it is done.
No, currently as you see from the code below thread updates every second as it sleeps inside f run method. I've also tried to use handlers to update UI thread, effect was the same. I also extended SeekBar to its own class, had thread there and this was no good either, exactly same effect.
If anyone can explain to me how to solve this problem and how its done with other player appls that would be great.
public class FightPlayerActivity extends Activity implements Runnable, OnSeekBarChangeListener, SurfaceHolder.Callback, OnPreparedListener {
private MediaPlayer mp=null;
private SeekBar seekBar;
private Thread progressBarUpdater;
private String filePath;
private Handler handler=new Handler();
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
Toast.makeText(this,"Create ", 2000).show();
}
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
// TODO Auto-generated method stub
}
public void onStop()
{
super.onStop();
mp.stop();
mp.reset();
mp.release();
}
public void run()
{
while(true)
{
try {
progressBarUpdater.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
seekBar.setProgress(mp.getCurrentPosition());
// handler does have same effect, so video stops for split second
//handler.postDelayed(this, 1000);
}
}
public void onStart()
{
super.onStart();
setContentView(R.layout.fight_player);
filePath=getIntent().getStringExtra("filename");
filePath=Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)+"/FightAll_BJJ_Scoring/"+filePath;
Toast.makeText(this,filePath, 2000).show();
// seek bar
seekBar=(SeekBar) findViewById(R.id.seek_bar);
seekBar.setOnSeekBarChangeListener(this);
try {
SurfaceView sv=(SurfaceView) findViewById(id.video_preview);
SurfaceHolder sh=sv.getHolder();
sh.addCallback(this);
sh.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalStateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void stop(View view)
{
mp.seekTo(0);
mp.pause();
}
public void pause(View view)
{
mp.pause();
}
public void play(View view)
{
mp.start();
}
public void surfaceCreated(SurfaceHolder holder) {
try {
mp=new MediaPlayer();
mp.setDataSource(filePath);
mp.setDisplay(holder);
mp.setOnPreparedListener(this);
mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
mp.prepare();
//handler.removeCallbacks(this);
//handler.postDelayed(this, 1000);
} catch (IllegalStateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void surfaceDestroyed(SurfaceHolder holder) {
// TODO Auto-generated method stub
}
public void onPrepared(MediaPlayer mediaplayer) {
mp.start();
seekBar.setMax(mp.getDuration());
progressBarUpdater=new Thread(this);
progressBarUpdater.start();
//handler.postDelayed(this, 1000);
}
public void onProgressChanged(SeekBar sb,int progress,boolean fromUser)
{
//Toast.makeText(this, progress, 2000).show();
mp.seekTo(progress);
}
public void onStartTrackingTouch(SeekBar seekBar) {
// TODO Auto-generated method stub
onProgressChanged(seekBar,seekBar.getProgress(),true);
}
public void onStopTrackingTouch(SeekBar seekBar) {
// TODO Auto-generated method stub
}
}
Your major problem is in your onProgressChanged() method.
You are seeking to the specified position every time the seekBar progress changes, even when it is done programmatically. Which means that every time you call seekBar.setProgress(mp.getCurrentPosition()), onProgressChanged() will be fired.
So we change it to the following:
public void onProgressChanged(SeekBar sb, int progress, boolean fromUser) {
if (fromUser) {
mp.seekTo(progress);
}
}
That way it will only be fired when the user moves the seekBar.
Moreover, according to this answer, it would be better to replace your while(true) loop with:
public void run() {
seekBar.setProgress(mp.getCurrentPosition());
if (mp.getCurrentPosition() < mp.getDuration()) {
seekBar.postDelayed(this, MILLISECONDS);
}
}
Related
I'm trying to create an app that play a certain audio file, that was previously recorded, when I press a button or shake my phone. Here is my code.
public class Reproduzir extends Activity implements SensorEventListener{
MediaPlayer player = new MediaPlayer();
SensorManager sensor;
#Override
protected void onCreate(Bundle savedInstanceState ){
super.onCreate(savedInstanceState);
sensor= (SensorManager) getSystemService(SENSOR_SERVICE);
setContentView(R.layout.reproduzir);
Button reproduzir = (Button) findViewById(R.id.reproduzir);
reproduzir.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
play();
}
});
Button fechar= (Button) findViewById(R.id.fechar);
fechar.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
if (player.isPlaying()) {
player.stop();
player.release();
}
finish();
}
});
}
public void play(){
try {
player.setDataSource(Environment.getExternalStorageDirectory().getPath()+"/2cp.3gp");
player.prepare();
player.start();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalStateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void onStart(){
super.onStart();
sensor.registerListener(this,sensor.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
SensorManager.SENSOR_DELAY_FASTEST);
}
#Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// TODO Auto-generated method stub
}
#Override
public void onSensorChanged(SensorEvent event) {
// TODO Auto-generated method stub
if(event.values[0]>10.2
||event.values[1]>10.2
||event.values[2]>10.2){
play();
}
}
}
My problem is that when I run it, I can only play it once. If I press the button a second time or shake it again, it does nothing. Can anyone help?
The easy fix is to simply add player.reset() in your play() method BEFORE calling player.setDataSource(...).
You can only call setDataSource(...) once without resetting the player. It is legal to call reset() in any state however (even if the player isn't yet initialized).
In other words, even if it's the first time you've called play() it is OK to use the following code in your play() method...
try {
player.reset();
player.setDataSource(Environment.getExternalStorageDirectory().getPath()+"/2cp.3gp");
player.prepare();
player.start();
}
// Your catch blocks here
Try to use:
mediaPlayer.reset()
after:
mediaPlayer.stop()
instead of release.
You can only set data source once.
in you code everytime the user presses the button you do this:
player.setDataSource(Environment.getExternalStorageDirectory().getPath()+"/2cp.3gp");
whice is wrong.
you need to setdata and prepare once and play how many times you want.
like so:
put this in you onCreate:
try {
player.setDataSource(Environment.getExternalStorageDirectory().getPath()+"/2cp.3gp");
player.prepare();
player.start();
in the onClick(View v), just have :
player.play();
Long story short - you have to use:
setLooping(true)
For more information about the Media Player check the post below. Take in mind the state diagram
http://developer.android.com/reference/android/media/MediaPlayer.html
MediaPlayer State diagram
I'm using a media controller associated with a media player to play a sound. The issue is that the media controller is hiding as soon as it loses focus. I have a button which when pressed, the sound is played and the media controller shows up at the bottom of the screen.
But when life pause is pressed, after like 5 seconds, the media controller itself hides.
here are part of my codes. I'm stopping the media player as soon as the media controller hides so that the sound is not continually played
All the codes for the media controller are:
mediaPlayer = new MediaPlayer();
mediaPlayer.setOnPreparedListener(currentFragment);
mediaController = new MediaController(getActivity()){
#Override
public void hide()
{
if(mediaPlayer.isPlaying()){
mediaPlayer.stop();
}
super.hide();
}
};
try {
mediaPlayer.setDataSource(sound.getSound());
mediaPlayer.prepareAsync();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalStateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
mediaPlayer.start();
#Override
public void onPrepared(MediaPlayer mp) {
Log.d("media status", "onPrepared");
handler.post(new Runnable() {
public void run() {
launchMediaController();
progress.dismiss();
}
});
}
void launchMediaController(){
mediaController.setEnabled(true);
mediaPlayer.start();
mediaController.setMediaPlayer(soundController);
mediaController.setAnchorView(getView());
mediaController.show(0);
}
private class SoundController implements MediaController.MediaPlayerControl{
public void start() {
mediaPlayer.start();
}
public void pause() {
mediaPlayer.pause();
}
public int getDuration() {
return mediaPlayer.getDuration();
}
public int getCurrentPosition() {
return mediaPlayer.getCurrentPosition();
}
public void seekTo(int i) {
mediaPlayer.seekTo(i);
}
public boolean isPlaying() {
return mediaPlayer.isPlaying();
}
public int getBufferPercentage() {
return 0;
}
public boolean canPause() {
return true;
}
public boolean canSeekBackward() {
return true;
}
public boolean canSeekForward() {
return true;
}
#Override
public int getAudioSessionId() {
// TODO Auto-generated method stub
return 0;
}
}
Edits:
Based on the answer of Libin, I did the following with no success:
Extends MediaController and overrides touch event
private class MyMediaController extends MediaController{
public MyMediaController(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
#Override
public boolean onTouchEvent(MotionEvent event) {
return true;
}
}
Use extended class
mediaController = new MyMediaController(getActivity()){
#Override
public void hide()
{
if(mediaPlayer.isPlaying()){
mediaPlayer.stop();
}
super.hide();
}
};
launchMediaController() is actually what launches the media controller.
My question, why does the media controller hides by itself after some seconds after the user touches it? Is there any solution to this?
when you call show() , you can set a duration to show the controller on screen. so the controller will automatically hide after 'timeout' milliseconds of inactivity.
You can set the timeout as 0 , to show the controller until the hide is explicitly called.
controller.show(0);
Use 0 to show the controller until hide() is called.
If you use show() method without argument , the default timeout is 3 seconds
Here is some code from show(int timeOut) method call
Message msg = mHandler.obtainMessage(FADE_OUT);
if (timeout != 0) {
mHandler.removeMessages(FADE_OUT);
mHandler.sendMessageDelayed(msg, timeout);
}
The handler is send a delayed message if the timeout id not 0, and in handler if the message is FADE_OUT the hide method is called
EDIT
There is bug in MediaController code. In the onTouchEvent the default timeout is set , which will override the timeout 0.
#Override
public boolean onTouchEvent(MotionEvent event) {
show(sDefaultTimeout);
return true;
}
You can fix this by creating a custom MediaController and override the onTouchEvent
#Override
public boolean onTouchEvent(MotionEvent event) {
return true;
}
To start off, playing and stopping music works fine. I've already searched SX, and have tried some of the solutions listed for this issue, but they haven't resolved my issue.
I have a "Replay Music" button, that is supposed to restart an .mp3 file from the beginning. I've got the necessary code in place (like reset(), prepare(), etc, in order for the media player to replay the music, but when the button is pushed, nothing happens.
I have all the OnClickListeners and other necessary things setup, it is just this one issue.
Below is the complete code for my "Replay Music" method. Any help will be gratefully received.
Just a thought: Perhaps it is something to do with the placement of the mediaPlayer.start() method call?
public void replaySoundButtonListener() {
Button testButton = (Button) findViewById(R.id.replayMusicButton);
testButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View arg0) {
mediaPlayer.reset();
try {
mediaPlayer.setDataSource("sdcard/InsomniMusic/insomnimix.mp3");
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalStateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
mediaPlayer.prepare();
} catch (IllegalStateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
mediaPlayer.start();
}
});
}
You only need to prepare and set the data source of a mediaplayer once. When you call reset the mediaplayer has to be reinitialised completely (set datasource again and call prepare). If you would like to reset the mediaplayer to the start of the song I guess you could use
mediaPlayer.setOnSeekCompleteListener(new MediaPlayer.OnSeekCompleteListener() {
#Override
public void onSeekComplete(MediaPlayer mediaPlayer) {
mediaPlayer.start();
}
});
If the song has already ended (or stopped otherwise), only mediaPlayer.start() will suffice (but I guess in your case you can never be sure of that).
More information on the start function here
I made a functions to start() stop() reset() and pause() in another Class
import android.content.Context;
import android.media.MediaPlayer;
public class AudioPlayer extends MediaPlayer {
private MediaPlayer mPlayer;
public void stop()
{
if(mPlayer!=null)
{
mPlayer.release();
mPlayer=null;
}
}
public void pause()
{
if(mPlayer.isPlaying()) {
mPlayer.pause();
}
else
{
mPlayer.start();
}
}
public void reset(Context c)
{
stop();
mPlayer=MediaPlayer.create(c, R.raw.one_small_step); //uzimamo .create(context,..) jer uzimamo iz res/raw lokalne datoteke
mPlayer.setOnCompletionListener(new OnCompletionListener() {
#Override
public void onCompletion(MediaPlayer mp) {
stop();
}
});
mPlayer.start();
}
public void play(Context c)
{
stop();
mPlayer=MediaPlayer.create(c, R.raw.one_small_step);
mPlayer.setOnCompletionListener(new OnCompletionListener() {
#Override
public void onCompletion(MediaPlayer mp) {
stop();
}
});
mPlayer.start();
}
}
//Here is my fragment
private Button mPlayButton;
private Button mStopButton;
private Button mPauseButton;
private Button mResetButton;
private AudioPlayer mPlayer=new AudioPlayer();
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v=getActivity().getLayoutInflater().inflate(R.layout.fragment_hello_moon, container);
mPlayButton=(Button)v.findViewById(R.id.hellomoon_playButton);
mPlayButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
mPlayer.play(getActivity());
}
});
mStopButton=(Button)v.findViewById(R.id.hellomoon_stopButton);
mStopButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
mPlayer.stop();
}
});
mPauseButton=(Button)v.findViewById(R.id.hellomoon_pauseButton);
mPauseButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
mPlayer.pause();
}
});
mResetButton=(Button)v.findViewById(R.id.hellomoon_resetButton);
mResetButton.setOnClickListener(new OnClickListener(
) {
#Override
public void onClick(View v) {
mPlayer.reset(getActivity());
}
});
return v;
}
#Override
public void onDestroy() {
super.onDestroy();
mPlayer.stop(); //Nakon sto je fragment unisten MediaPlayer nastavlja radit jer je na drugacijem Threadu pa treba pozvati Stop();
}
I have been stuck with this problem for quite a while now. In the mediaPlayer i've created everything runs smooth in the initial playing stage but when the user selects the next or previous option, the application gives me a (0,-107) error along with the attempt to call getDuration without a valid mediaplayer
The code:
public void playPodCast(int index) {
try {
urlToSend = toGet.get(index).get("URL").toString();
mediaPlayer.reset();
mediaPlayer.setDataSource(urlToSend);
mediaPlayer.prepareAsync();
onPlayerLoad();
mediaPlayer.setOnPreparedListener(this);
mediaPlayer.setOnErrorListener(this);
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
here the onPlayerLoad() is just a method to hide all the views while the mediaPlayer is loading.
#Override
public void onPrepared(MediaPlayer mp) {
// TODO Auto-generated method stub
onPlayerLoaded();
updateProgressBar();
mediaPlayer.start();
}
the onPlayerLoaded() loads back on the view.
public void updateProgressBar() {
seekHandler.postDelayed(updateSeekTime, 100);
}
private Runnable updateSeekTime = new Runnable() {
#Override
public void run() {
// TODO Auto-generated method stub
long totalDuration = mediaPlayer.getDuration();
long currentDuration = mediaPlayer.getCurrentPosition();
// Displaying the total time duration
seekRight.setText("" + utils.milliSecondsToTimer(totalDuration));
// Displaying completed playing time
seekLeft.setText("" + utils.milliSecondsToTimer(currentDuration));
// Updating the SeekBar progress
int progress = (int) (utils.getProgressPercentage(currentDuration,
totalDuration));
seekBar.setProgress(progress);
seekHandler.postDelayed(this, 100);
}
};
#Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
// TODO Auto-generated method stub
}
// #Override
public void onStartTrackingTouch(SeekBar seekBar) {
// TODO Auto-generated method stub
seekHandler.removeCallbacks(updateSeekTime);
}
#Override
public void onStopTrackingTouch(SeekBar seekBar) {
// TODO Auto-generated method stub
seekHandler.removeCallbacks(updateSeekTime);
int totalDuration = mediaPlayer.getDuration();
int currentPosition = utils.progressToTimer(seekBar.getProgress(),
totalDuration);
// forward or backward to certain seconds
mediaPlayer.seekTo(currentPosition);
// update timer progress again
updateProgressBar();
}
Here, private Handler seekHandler = new Handler();
Like I said before, the initial call to playPodCast(index) does not give me any errors, it works very smoothly. But when the user clicks on the next and previous buttons, the error is generated. The next and previous methods do not give out any errors if implemented without the updateProgressBar(), everything runs unbuggyly. So why am I getting this error ? I have no clue why ... please do help.
Here's the implementation of the next button:
btnNext.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
if (getPosition == totalList - 1) {
getPosition = 0;
playPodCast(getPosition);
} else {
getPosition = getPosition + 1;
playPodCast(getPosition);
}
}
});
where getPosition is the position of the media file being referenced.
And I also did try
#Override
public void onPrepared(MediaPlayer mp) {
// TODO Auto-generated method stub
mediaPlayer.start();
onPlayerLoaded();
updateProgressBar();
}
Update 1:
I found that the error occurs when using the prepareAsync(), the program works fine on prepare() but is super laggy.
The problem is getDuration is getting called by your updateSeekTask while the new media is preparing. This will error. What's the duration of an unprepared video?
The solution is to cancel any callbacks of the updateSeekTask when the user clicks next, and reinstate them once the media is ready.
public void onClick(View arg0) {
if (getPosition == totalList - 1) {
getPosition = 0;
seekHandler.removeCallbacks(updateSeekTime); // Cancel me!
playPodCast(getPosition);
} else {
getPosition = getPosition + 1;
seekHandler.removeCallbacks(updateSeekTime); // Cancel me!
playPodCast(getPosition);
}
}
Then in onPrepared restart the task as you do currently:
public void onPrepared(MediaPlayer mp) {
mediaPlayer.start();
onPlayerLoaded();
updateProgressBar();
}
You should still use prepareAsync. If you just use prepare everything will wait until the media is prepared. This is the lag you experience. It will fix your problem (since even the task will wait) but it's not recommended.
Instead of calling the following:
int totalDuration = mediaPlayer.getDuration();
inside the Runnable updateSeekTime() make the totalDuration a global variable and refer initialize the value of the totalDuration inside the onPrepared(MediaPlayer mp) like this:
#Override
public void onPrepared(MediaPlayer mp) {
// TODO Auto-generated method stub
onPlayerLoaded();
totalDuration = mp.getDuration() // mp is the MediaPlayer instantiated by this method
updateProgressBar();
mediaPlayer.start();
}
and Presto you are done ... Hope this helps someone.
I use media player to play sounds on my application. It works just fine.
I am playing sound on a separated thread. Even thought, part of the sound plays before activity appear.
I tried to play sound onCreate method. it didn't work. onStart and
onResume. it has some problems. it plays every time activity resumed.
sometimes while I am not even touching the device!
What is the best way to play sound after activity appears?
public boolean played = false;
#Override
protected void onResume() {
// TODO Auto-generated method stub
super.onResume();
if (!played)
Settings.playSound(dvd.titleImageName.replace("png", "mp3"), this);
played = true;
}
public static MediaPlayer mp = null;
public static void playSound(String fileName, Context c)
{
//MediaPlayer mp = MediaPlayer.create(c, resId);
if (mp!=null)
{
mp.stop(); //error
mp.reset();
mp.release();
}
mp = new MediaPlayer();
AssetFileDescriptor descriptor;
try {
descriptor = c.getResources().getAssets().openFd("sounds/" + fileName);
mp.setDataSource(descriptor.getFileDescriptor(), descriptor.getStartOffset(), descriptor.getLength());
descriptor.close();
mp.prepare();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (mp == null) return;
mp.setOnCompletionListener(new OnCompletionListener() {
#Override
public void onCompletion(MediaPlayer mp) {
}
});
mp.setOnPreparedListener(new OnPreparedListener() {
#Override
public void onPrepared(MediaPlayer mp) {
// TODO Auto-generated method stub
}
});
mp.setOnVideoSizeChangedListener(new OnVideoSizeChangedListener() {
#Override
public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
// TODO Auto-generated method stub
}
});
new Thread(new Runnable() {
#Override
public void run() {
// TODO Auto-generated method stub
mp.start();
}
}).start();
}
Your played variable only works while activity is alive. If it gets killed, played doesn't retain. For example, rotate your device an you'll hear sound playing again.
save state (played) in onSavedInstanceState bundle and restore it in onCreate
Play in in OnCreate or onResume