Playing video in TextureView crash when resuming from onPause - android

If I pause my activity, on returning I get a crash
Caused by: java.lang.IllegalStateException
at android.media.MediaPlayer.prepareAsync(Native Method)
I want to be able to return to exactly where the video left off and continue from there. I also want it to survive orientations, which it is not doing right now. Thanks for any hint on how to accomplish this. I am not using ExoPlayer because I want to stay with API 14, instead of bumping to API-16 and lose some users.
My setup is Activity -> Fragment {TextureView}. In portrait the fragment is half of the screen; in landscape the fragment is fullscreen.
code snippet
#Override
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
surface = new Surface(surfaceTexture);
playVideoNow();
}
private void playVideoNow() {
if(null== textureView || null==surface || null==mCurrVideo) return;
try {
final String url = mCurrVideo.getUrl();
mediaPlayer = new MediaPlayer();
mediaPlayer.setDataSource(getContext(), Uri.parse(url));
mediaPlayer.setSurface(surface);
mediaPlayer.setLooping(false);
mediaPlayer.prepareAsync();
mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
#Override
public void onPrepared(MediaPlayer player) {
mediaPlayer.start();
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
playVideoNow() is called from multiple possible places such as onResume on BroadcastReceiver, hence the checks to see if everything is in order. Also the

Related

android app with multiple media players in same activity - how to use efficiently

I am creating an android activity that will have many mediaplayers inside. The activity will have many mediaplayer objects and if i could describe it its like a
4 X 1 grid of media players. I created the 4X1 grid by using TextureView class's in android.
so the xml layout for the activity looks like this more or less:
<TextureView
android:id="#+id/surface_one"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
<TextureView
android:id="#+id/surface_two"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
<TextureView
android:id="#+id/surface_three"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
<TextureView
android:id="#+id/surface_four"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</LinearLayout>
So each textureView will be the surface where the MediaPlayers will play there video.
programatically i've set up SurfaceTextureListener's so that i know when the surface is available like this for each one:
TextureView tv1 = (TextureView)findViewById(R.id.surface_one);
tv1.setSurfaceTextureListener(new SurfaceListener_1());
and likewise for the rest. here is how i would do the second texture view setup:
TextureView tv2 = (TextureView)findViewById(R.id.surface_two);
tv2.setSurfaceTextureListener(new SurfaceListener_2());
Then in code i am creating 4 MediaPlayer Objects and setting there surface like this for mediaplayer #1:
private class SurfaceListener_1 implements TextureView.SurfaceTextureListener {
#Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
Surface sfc = new Surface(surface);
try {
m_mp1 = new MediaPlayer();
m_mp1.setSurface(sfc); //critical , here i am adding the surface so the media player can play on it
m_mp1.setDataSource(m_context, m_videoUri_one);
m_mp1.prepareAsync();
m_mp1.setLooping(true);
m_mp1.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
#Override
public void onPrepared(MediaPlayer mp) {
video1Prepared = true;
}
});
m_mp1.setOnErrorListener(new MediaPlayer.OnErrorListener(){
#Override
public boolean onError(MediaPlayer mp, int what, int extra) {
return true;
}
});
} catch (Exception e) {
e.printStackTrace();
}
finally {
//release the surface as its now set in the media player
if(sfc!=null)
sfc.release();
}
}
//...... the rest of the call backs not important...
and for the other media players i repeat the same thing so that they all have surfaces to play on. here is how i would do mediaplayer # 2:
private class SurfaceListener_2 implements TextureView.SurfaceTextureListener {
#Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
Surface sfc = new Surface(surface);
try {
m_mp2 = new MediaPlayer();
m_mp2.setSurface(sfc); //critical , here i am adding the surface so the media player can play on it
m_mp2.setDataSource(m_context, m_videoUri_two);
m_mp2.prepareAsync();
m_mp2.setLooping(true);
m_mp2.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
#Override
public void onPrepared(MediaPlayer mp) {
video2Prepared = true;
}
});
m_mp2.setOnErrorListener(new MediaPlayer.OnErrorListener(){
#Override
public boolean onError(MediaPlayer mp, int what, int extra) {
return true;
}
});
} catch (Exception e) {
e.printStackTrace();
}
finally {
//release the surface as its now set in the media player
if(sfc!=null)
sfc.release();
}
}
//...... the rest of the call backs not important...
so in the end what i have created is 4 surfaces where i can play a video. All 4 media players play different video files.
Once all the media players are prepared, i call their start() method on all of them and they play at the same time.
The issue i am facing is that this can be cpu intensive. On some devices i can get a ANR or the app just gets slow to respond.
The media file i am playing is a MP4 and the size of the media files are all about 9 MB.
Is there anything you can recommend so that i can be more cpu efficient in this already heavy task ? For example, can i get the GPU to help ?
Or if i change media type will that make it more efficient ? the video's do not have sound there only visual if that helps.

Bizarre Invalidate behaviour

I have a custom viewgroup (Custom2) inside a custom viewgroup (Custom1).
Custom1 has an imageview displaying a RectShape Shapedrawable and two textviews.
I set an onClickListener in Custom2 which removes the imageview using this.removeViewAt(0) and creates a new TextureView, then calling this.addView (mTextureView, 0) and finally invalidate. I then get the textureview to play a video using mediaplayer.
Here is the code:
public void setToTextureView() {
//TextureView when not debugging
TextureView View;
View = (TextureView) new TextureView(mContext);
View.setLayoutParams(new LayoutParams(600, 500));
this.removeViewAt(0);
this.addView(View, 0);
View.invalidate();
Log.d("Debug", "Invalidate Called");
View.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
#Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
Surface mSurface = new Surface(surface);
MediaPlayer mMediaPlayer = new MediaPlayer(); //Debug MediaPlayer issues by setting ItemToDebug = "MediaPlayer"
try {
mMediaPlayer.setDataSource(mContext, Uri.parse("android.resource://" + mContext.getPackageName() + "/" + R.raw.t));
if (ItemToDebug.equals("MediaPlayer")) {
Log.d("MediaPlayer", "Data source set");
}
mMediaPlayer.setSurface(mSurface);
if (ItemToDebug.equals("MediaPlayer")) {
Log.d("MediaPLayer", "Surface set");
}
mMediaPlayer.prepare();
if (ItemToDebug.equals("MediaPlayer")) {
Log.d("MediaPLayer", "Prepared");
}
} catch (IOException e) {
Log.d("IOException", e.getMessage());
}
mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
#Override
public void onPrepared(MediaPlayer mp) {
if (ItemToDebug.equals("MediaPlayer")) {
Log.d("Debug", "Media Player Prepared");
}
mp.start();
}
});
}
Now, I notice bizarrely that the ImageView disappears and an empty space appears (of the correct size) when I click the view, but no video plays, although the code appears to execute fine upto invalidate();
However, I can then get the TextureView to get a surfaceTexture and play the video when I interrupt the program and go back in (i.e by pressing the button that takes you to the screen with all the open programs, and selecting my activity again.)
I don't have a good understanding of exactly what is happening but I am pretty sure that no SurfaceTexture is being created because onAttached() isn't being called.
Does anybody know what might be going on?
Cheers,
J
The view disappears, I need to call requestLayout() in order to draw something else there.

Media Player Class in Android the implementation scenario

for last three weeks I have worked on a Media Player in Android.I am trying to find a solution of how can I make my Media Player to change the song when it's already playing one.
Here is my Listener on the RecyclerView
musicList.addOnItemTouchListener(
new RecyclerItemClickListener(getApplicationContext(), new RecyclerItemClickListener.OnItemClickListener() {
#Override
public void onItemClick(View view, final int position) {
currentPosition = position;
if(!mediaPlayer.isPlaying()){
musicThread.start();
} else {
mediaPlayer.reset();
}
}
})
);
}
and my Thread is this:
final Thread musicThread = new Thread(new Runnable(){
#Override
public void run() {
try {
URL = getMusicURL(myDataset[currentPosition]);
try {
mediaPlayer.setDataSource(URL);
//mediaPlayer.prepare(); // might take long! (for buffering, etc)
mediaPlayer.prepareAsync(); // prepare async to not block main thread
} catch (IOException e) {
e.printStackTrace();
Log.i("TEST","Eroare: "+e.getMessage());
}
} catch (StorageApiException e) {
e.printStackTrace();
Log.i("TEST","Eroare: "+e.getMessage());
}
}
});
I think you have a mess. First of all, you dont need a thread to play music, the own mediaplayer API does it for you when you call mediaPlayer.start(). However, you have to care about the time it takes to prepare the data source if you are for example streaming online music. For this, just use mediaPlayer.prepareAsync() and register a callback. When it has finished preparing, you can automatically start playing or do whatever you want.
If you want to change the data source, just follow the automaton map that you can find in MediaPlayer docs. Essentially, when the user selects another track, you register the call in your button listener, then reset the mediaPlayer, and recall all prepare, start... cycle again. By the way, it is advised to deploy all your mediaplayer code into a service so that it can keep playing even though the user has closed your activity.

How to detect when VideoView starts playing (Android)?

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);

Strange behaviour with mediaplayer and seekTo

I'm implementing a custom video player because I need custom video controls. I have an app with only one activity, which on startup shall start playing a video right away.
Now, the problem I have is:
I don't want the video to start from the beginning, but from a later position. Therefore I do a seekTo(16867). Since seekTo is asynchronous, I place the start call of the mediaplayer (player.start()) in the onSeekComplete of the onSeekCompleteListener.
The strange behaviour I experience though is that I can see/hear the video playing from the beginning for a few millisecs before it actually plays from/jumps to the position I seeked to.
But - on the other hand - the Log output I call before the player.start returns the correct position 16867, where I seeked to.
Below is the relevant code section, the complete class is at http://pastebin.com/jqAAFsuX
(I'm on Nexus One / 2.2 StageFright)
private void playVideo(String url) {
try {
btnVideoPause.setEnabled(false);
if (player==null) {
player=new MediaPlayer();
player.setScreenOnWhilePlaying(true);
}
else {
player.stop();
player.reset();
}
url = "/sdcard/myapp/main/videos/main.mp4"; // <--- just for test purposes hardcoded here now
player.setDataSource(url);
player.setDisplay(holder);
player.setAudioStreamType(AudioManager.STREAM_MUSIC);
player.setOnCompletionListener(this);
player.setOnPreparedListener(this);
player.setOnSeekCompleteListener(new MediaPlayer.OnSeekCompleteListener() {
public void onSeekComplete(MediaPlayer mediaPlayer) {
Log.d("APP", "current pos... "+ player.getCurrentPosition() );
player.start(); // <------------------ start video on seek completed
player.setOnSeekCompleteListener(null);
}
});
player.prepareAsync();
}
catch (Throwable t) {
Log.e(TAG, "Exception in btnVideoPause prep", t);
}
}
public void onPrepared(MediaPlayer mediaplayer) {
width=player.getVideoWidth();
height=player.getVideoHeight();
if (width!=0 && height!=0) {
holder.setFixedSize(width, height);
progressBar.setProgress(0);
progressBar.setMax(player.getDuration());
player.seekTo(16867); // <------------------ seeking to position
}
btnVideoPause.setEnabled(true);
}
Since I also haven't recieved any reply from the Android developer list and found similar issues on the web, I conclude it's a bug. I filed a bug report at http://code.google.com/p/android/issues/detail?id=9135

Categories

Resources