Using the Vitamio media player, I do not see a constant for when the video actually starts rendering (as there has been for the normal android MediaPlayer since api 17). onPreparedListeners do not detect when the rendering physically starts, and, as a result, the black screen prior to the video starting is seemingly unavoidable.
Is there any way to detect when the video has actually started rendering in Vitamio?
Though it's a bit of a hack, I found that this way works wonders:
Create a boolean, defaulted to false, which determines whether or not buffering has completed for the first time.
Set an onInfoListener on your Vitamio VideoView.
Look for MediaPlayer.MEDIA_INFO_BUFFERING_END
If the boolean you created is false, then set it to true and wait, with a while loop, until yourVideoView.getCurrentPosition() != 0. Note that 0 may be too low -- sometimes getCurrentPosition will return a number higher than zero before it has started, but typically the returned value will be no higher than 1000 / (the fps of your video). I used 40.
Execute desired code (remove other views to make the VideoView visible, or add the VideoView to the layout).
In an OnCompletionListener, set the created boolean back to false.
public class AnimationCanvas extends VideoView{
public static AnimationCanvas animationCanvas;
private static boolean bufferedOnce = false;
public AnimationCanvas(Context context, AttributeSet attrs){
super(context, attrs);
animationCanvas = this;
getHolder().addCallback(this);
this.setOnInfoListener(new MediaPlayer.OnInfoListener() {
#Override
public boolean onInfo(MediaPlayer mp, int what, int extra) {
if (!bufferedOnce && what == MediaPlayer.MEDIA_INFO_BUFFERING_END) {
bufferedOnce = true;
while (getCurrentPosition() < 40) {
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
MainActivity.frameLayout.removeView(MainCanvas.mainCanvas);
return true;
}
return false;
}
});
this.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
#Override
public void onCompletion(MediaPlayer mp) {
bufferedOnce = false;
MainActivity.frameLayout.addView(MainCanvas.mainCanvas);
MainActivity.frameLayout.removeView(animationCanvas);
}
});
}
EDIT: Note that another option is to create a separate Runnable which does the waiting (while(vv.getCurrentPosition() < 40){}) and then, in the same Runnable, call runOnUIThread() to run a second runnable which alters / removes / adds views if needed (Views can only be touched by the thread in which they were created.) This way, there is no need for an onInfoListener -- just start the first Runnable in an onPreparedListener.
Related
I'm trying to have a SoundPool play while a view object is being clicked.
I have successfully implemented it but found out that the sound of the click will lag.
I've been researching on StackOverFlow and on the internet for solutions and the best solution seems to be to have the SoundPool on a separate Thread.
I'm very new to the whole Thread thing and so I don't really know what to do at the moment.
I have followed THIS guide on creating a SoundPool thread, however, I don't know how to access it, or even to assign the clips that I wished to play.
Can someone please give me an example of how I should call the SoundPool thread to play my clip while an object in the view is being clicked.
Please find my onClick code below:
#Override
public boolean onTouch(View v, MotionEvent event) {
int actionPerformed = event.getAction();
switch(actionPerformed) {
case MotionEvent.ACTION_DOWN: {
if (Math.pow((event.getX() - a_Ball.getX()),2)
+ Math.pow((event.getY() - a_Ball.getY()), 2)
<= Math.pow(a_Ball.getDiameter(),2)) {
//pCheck = !pCheck;
if (pauseBall == false) {
SoundPool soundPool = new SoundPool(1,AudioManager.STREAM_MUSIC,1);
clickSound = soundPool.load(MainActivity.this, R.raw.click, 1);
/*soundPool.setOnLoadCompleteListener(new OnLoadCompleteListener() {
public void onLoadComplete(SoundPool soundPool, int sampleId,int status) {
soundPool.play(clickSound,1.0f, 1.0f, 0, 0, 1.0f);
}
});*/
//Increase Ball Speed
sIncrease = true;
}
} else {
if (pauseBall == false) {
//set minus health when not touch on ball
health--;
TextView txtHealth = (TextView)findViewById(R.id.health);
String tempHealth = txtHealth.getText().toString();
tempHealth = getResources().getString(R.string.health) + String.valueOf(health);
txtHealth.setText(tempHealth);
}
}
break;
}
default:
return false;
}
return true;
} //onTouch end
Thanks in advance.
You don't need threads to make sounds without lag. The lag comes from the loading of the sound file, try create the SoundPool and load the sound (soundPool.load) in advance (e.g. onCreate), then just call soundPool.play in your onTouch method.
Ok, so instead of doing it in another class, I decided to do it in one class and created a new thread by myself. Seems to be working well enough.
I'll mark this thread as answer.
I have many short audio fragments which should be played sequentially with minimum latency.
How can I queue these sounds efficiently?
There are two ways I can see this working: using a MediaPlayer or a SoundPool
MediaPlayer
You can use MediaPlayer.OnCompletionListener combined with a list of files to simply listen for completion and play the next one. By and large this works quite well and I have used it to play sequences of very short clips (< 1s) without problems.
SoundPool
Another way is to use the SoundPool class combined with a handler to simply queue up play events in advance. This assumes you know the length of each clip but depending on the format you should be able to find this out.
Which solution you choose depends on a number of factors: how many files, how short they are, what format they are in, where you are getting them from, how fixed or variable the list is, etc.
Personally I would go with MediaPlayer as this is easier to implement and would probably fit your needs.
One way is to concatenate them into a single audio file, then create a MediaPlayer for it and set an OnSeekCompleteListener. Seek to each segment in tern in whichever order you like and then play them in onSeekComplete(). It's timing not exact and MediaPlayer is touchy to use so it may not be the best choice for you but it's good enough for my purposes.
Here's my code:
private MediaPlayer MP = new MediaPlayer();
#Override
public void onCreate(Bundle savedInstanceState) {
//...
FileDescriptor fd = getResources().openRawResourceFd(R.raw.pronounciations).getFileDescriptor();
try {
setVolumeControlStream(AudioManager.STREAM_MUSIC); // Lets the user control the volume.
MP.setDataSource(fd);
MP.setLooping(false);
MP.prepare();
MP.start(); // HACK! Some playing seems required before seeking will work.
Thread.sleep(60); // Really need something like MediaPlayer.bufferFully().
MP.pause();
MP.setOnErrorListener(new OnErrorListener() {
public boolean onError(MediaPlayer mp, int what, int extra) {
return false;
}
});
MP.setOnSeekCompleteListener(new OnSeekCompleteListener() {
public void onSeekComplete(MediaPlayer mp) {
// The clip is queued up and read to play.
// I needed to do this in a background thread for UI purposes.
// You may not need to.
new Thread() {
#Override
public void run() {
MP.start();
try {
Thread.sleep(soundRanges[curWordID][1]);
} catch(InterruptedException e) {}
MP.pause();
}
}.start();
}
});
} catch(Throwable t) {}
}
private void playCurrentWord() {
if(soundRanges != null && !MP.isPlaying() && !silentMode) {
try {
MP.seekTo(soundRanges[curWordID][0]);
}
catch(Throwable t) {}
}
}
You would likely need to concatenate your clips using an external sound editing tool. I used Audacity to find and edit the clip beginnings and lengths that I saved in a file.
soundRanges[curWordID][0] is the offset in the sound file of the beginning of a clip and soundRanges[curWordID][1] is its length.
I'm trying to implement the restart of MediaPlayer in Android, when errors happen (connection with server lost, network is unreachable and other).
I've seen many code examples, but all are somewhat non-standard. I think there must be the standard way to restart corresponding to the developer.android.com, but it's not clear from here, how to set the listener which would restart player on such errors.
Here are the parts of my code:
public class PlayerService extends Service implements OnErrorListener {
....
////////////////////
this.mplayer = MediaPlayer.create(c, Uri.parse(url));
mplayer.setOnErrorListener(onErrorListener);
////////////////////
MediaPlayer.OnErrorListener onErrorListener = new MediaPlayer.OnErrorListener()
{
#Override
public boolean onError(MediaPlayer mp, int what, int extra)
{
Log.e(getPackageName(), String.format("Error(%s%s)", what, extra));
playlist="ERROR";
restart();
return true;
}
};
#Override
public boolean onError(MediaPlayer player, int what, int extra) {
restart();
return true;
};
public void restart()
{
try
{
playlist="RELOADING";
for (int u=1; u<=5; u++)
{
Thread.sleep(5000);
mplayer.stop();
mplayer.release();
mplayer=null;
playSong(getApplicationContext(),currenturl);
};
}
catch (Exception e)
{
playlist="RELOADING ERROR";
}
}
//////////////
....
}
Am I setting the listener right? I'm not sure where to put onError function so I have 2 of them. When I emulate the error by setting the phone to the flight mode, the listener fires "RELOADING" and "RELOADING ERROR" title. But after the network is on, no restart of the player happens. There is no sound.
What's wrong here? The player cannot restart.
Please help to make the code workable. Also can be connection skips and IO Exception.
Overview
I ran into a similar issue and based on the documentation it indicates that all you need to do is reset your media player:
In order to reuse a MediaPlayer object that is in the Error state and recover from the error, reset() can be called to restore the object to its Idle state.
What you are currently doing is stopping and releasing (mplayer.stop() and mplayer.release()) a media player that is in the Error state. This should be causing something like an IllegalStateException to be raised. If it's not throwing an error you would still be trying to start a song in a null object. Instead of calling stop and release then setting the variable to null you should be using the mplayer.reset() function.
Another option would be to initiate a new media player but the documentation details the subtle difference between a newly instantiated MediaPlayer object and one that has had reset() called on it.
Reset after Error
Based on this information something like the following should fix your issue:
public boolean onError(MediaPlayer mp, int what, int extra)
{
Log.e(getPackageName(), String.format("Error(%s%s)", what, extra));
playlist="ERROR";
if(what == MediaPlayer.MEDIA_ERROR_SERVER_DIED)
mp.reset();
else if(what == MediaPlayer.MEDIA_ERROR_UNKNOWN)
mp.reset();
// Deal with any other errors you need to.
// I'm under the assumption you set the path to the song
// and handle onPrepare, start(), etc with this function
playSong(getApplicationContext(),currenturl);
mplayer.setOnErrorListener(this);
mplayer.setOnCompletionListener(this);
mplayer.setOnPreparedListener(this);
return true;
}
See media player constant documentation for a list of potential errors.
Setting Error Listener
As for setting the error listener, here is how I've implemented it in the past:
public class MediaPlayerActivity extends Activity implements OnCompletionListener,
OnPreparedListener, AnimationListener, OnErrorListener{
private MediaPlayer mediaPlayer;
#Override
public boolean onError(final MediaPlayer arg0, final int arg1, final int arg2) {
// Error handling logic here
return true;
}
protected void onResume(){
super.onResume();
// do some onResume logic
mediaPlayer.setOnErrorListener(this);
mediaPlayer.setOnCompletionListener(this);
mediaPlayer.setOnPreparedListener(this);
// finish on resume and start up media player
}
}
I then handle loading up the media player in another function initiated by onResume().
this following source code snippet is given:
videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
#Override
public void onPrepared(MediaPlayer mediaPlayer) {
mediaPlayer.setOnInfoListener(new MediaPlayer.OnInfoListener() {
#Override
public boolean onInfo(MediaPlayer mp, int what, int extra) {
if (what == MediaPlayer.MEDIA_INFO_BUFFERING_END){
activity.dismissDialog(DialogID.DIALOG_LOADING);
return true;
}
return false;
}
});
}
});
I am streaming HLS streams with Android 3.x+ devices and trying to hide a loading dialog once the buffering is completed.
The video streaming works, but the info events are never fired.
Any ideas?
I know its too late, But posting it for the users still seeking for the solution (This worked for me):
progressDialog.show();
videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
#Override
public void onPrepared(MediaPlayer mediaPlayer) {
mediaPlayer.setOnInfoListener(new MediaPlayer.OnInfoListener() {
#Override
public boolean onInfo(MediaPlayer mp, int what, int extra) {
if (what == MediaPlayer.MEDIA_INFO_BUFFERING_END){
progressDialog.dismiss();
return true;
} else if(what == MediaPlayer.MEDIA_INFO_BUFFERING_START){
progressDialog.show();
}
return false;
}
});
progressDialog.dismiss();
videoView.start();
}
});
You're right, the events are never fired. This is a known HLS bug that I don't think Google will fix.
This applies to the onInfo and the buffering events.
See https://code.google.com/p/android/issues/detail?id=42767 and https://code.google.com/p/googletv-issues/issues/detail?id=2
Sorry!
Not fully sure as to what the OP is asking, but here are some very untimely bits of information.
I wouldn't rely on onPrepared. I find it to be unreliable.
I have found the two most useful pieces of information for HLS streaming through the MediaPlayer are the duration of the video and the progress position of the video. You get both of these by listening to progress updates.
When the duration is greater than zero, you know the video is truly prepared and can be manipulate (scrub). When progress position changes, you know the video is done buffering and has commenced playback. This last item only works when the video is playing of course. The MediaPlayer tends to relay inaccurate information.
These pieces of information are mostly accurate and can usually be relied upon to be "fairly" timely. This timeliness varies from device to device.
onPrepared is called when the MediaPlayer is prepared to start buffering, not when the video is completely buffered. However, it is completely natural to dismiss the loading dialog from within the onPrepared method.
Also MEDIA_INFO_BUFFERING_END is used when MediaPlayer is resuming playback after filling buffers, so I do not think it should be something to use to dismiss the dialog. So this should work:
videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
#Override
public void onPrepared(MediaPlayer mediaPlayer) {
activity.dismissDialog(DialogID.DIALOG_LOADING);
}
});
You can able to set OnPreparedListener on videoView because its your object but if you checkout source of VideoView you will find that mMediaPlayer is its private member so any change that you do from external will not be applied to it.
As per your requirement you need buffering status so you can have thread or handler or some thing so you can update your UI to get buffer status there is one method
int percent = videoView.getBufferPercentage();
if(percent == 100){
// buffering done
}
You no need to go through setOnInfoListener
by overriding setOnPreparedListener method is enough. as in the api show
public void setOnPreparedListener (MediaPlayer.OnPreparedListener l)
Register a callback to be invoked when the media file is loaded and
ready to go.
so, you can dismiss your dialog inside setOnPreparedListener method is enough
like this
vv.setOnPreparedListener(new OnPreparedListener() {
#Override
public void onPrepared(MediaPlayer mp) {
handler.post(new Runnable() {
#Override
public void run() {
Toast.makeText(MainActivity.this, "finish11", Toast.LENGTH_LONG).show();
}
});
}
});
If you want to show loading each time it's buffering (initial time or subsequent buffer underruns) just ensure to show it again:
// at the beginning
show
boolean onInfo(int what, int extra) {
switch (what) {
case MEDIA_INFO_BUFFERING_END:
"hide";
break;
case MEDIA_INFO_BUFFERING_START
"show":
}
}
So this event sequence will do as desired:
- whenever you start (setVideoURI or start): show
- onPrepared: just plug the info listener
- onInfo BUFFERING_END hide (it's playing)
- onInfo BUFFERING_START show (it's buffering again)
- onInfo BUFFERING_END hide (it's playing)
Update:
This is assuming the info events work. Of course.
Here's the problem, I want to change play button to pause button when the video stream starts playing in videoview but I don't know how to detect that event?
There is a great article about MediaPlayer in here - http://www.malmstein.com/blog/2014/08/09/how-to-use-a-textureview-to-display-a-video-with-custom-media-player-controls/
You can set infoListener on your VideoView
setOnInfoListener(new MediaPlayer.OnInfoListener() {
#Override
public boolean onInfo(MediaPlayer mp, int what, int extra) {
if (what == MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START) {
// Here the video starts
return true;
}
return false;
}
I ended up using VideoView.setOnPreparedListener. This was enough to cover my problem (play button drawable change to pause)
accepted answer here is not 100% accurate.
sometimes onprepared is call 3 seconds before first frame is being rendered. i suggest having a callback on that event (MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START)
mMediaPlayer.setOnInfoListener(new MediaPlayer.OnInfoListener() {
#Override
public boolean onInfo(MediaPlayer mediaPlayer, int i, int i1) {
if (i == MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START){
//first frame was bufered - do your stuff here
}
return false;
}
});
see media info documantaion for more callbacks of info/warning:
https://developer.android.com/reference/android/media/MediaPlayer.html#MEDIA_INFO_VIDEO_RENDERING_START
As far as I know, there is no event sent when video start playing in VideoView, but I can think of two options:
Create a version of VideoView by yourself, that sends event in those cases.
Use MediaController (which is default in VideoView)
If you want to follow option one - you can get the source of VideoView from here
isPlaying() can be called to test whether the MediaPlayer object is in the Started
Android MediaPlayer.isPlaying()
Another way to detect if videoview started is using videoView.getCurrentPosition(). getCurrentPosition() returns 0 if streaming not started.
protected Runnable playingCheck = new Runnable() {
public void run() {
while (true) {
if (vw.getCurrentPosition() != 0) {
// do what you want when playing started
break;
} else {
try {
Thread.sleep(250);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
};
Then call:
new Thread(playingCheck).start();
Please try this :
final Handler h = new Handler();
h.postDelayed( new Runnable() {
public void run() {
if (videoView.getCurrentPosition() != 0) {
((ProgressBar) rootView.findViewById(R.id.pgStreaming)).setVisibility(View.GONE);
} else {
h.postDelayed(this, 250);
}
}
}, 250);