I'm building an app where the user clicks on a button to show a video full screen. Initially the video is attached to a view inside a ViewPager. To be able to show it fullscreen I detach it from its parent and reattach it to the root view. This works fine, except when the video is switched to fullscreen while playing. When I detach a playing VideoView it just stop and I need to restart it. This is not acceptable since the video starts buffering before resume. Here the part of the code where the detach is done:
final ViewGroup parent = (ViewGroup) findViewById(R.id.parent);
final ViewGroup root = (ViewGroup) findViewById(R.id.root);
Button b = (Button) findViewById(R.id.button);
b.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
parent.removeView(mVideoView);
LayoutParams lp = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
root.addView(mVideoView, lp);
}
});
Depending of the device, I have a different log error. Probably because the actual video player is provided by the manufacturer and not the Android SDK. Here are the error logs for a Nexus 7:
10-30 20:26:18.618: D/NvOsDebugPrintf(124): NvMMDecTVMRDestroyParser Begin
10-30 20:26:18.618: D/NvOsDebugPrintf(124): --------- Closing TVMR Frame Delivery Thread -------------
10-30 20:26:18.678: D/NvOsDebugPrintf(124): ------- NvAvpClose -------
10-30 20:26:18.678: D/NvOsDebugPrintf(124): NvMMDecTVMRDestroyParser Done
10-30 20:26:18.678: D/NvOsDebugPrintf(124): NvMMLiteTVMRDecPrivateClose Done
I haven't been able to detach the video without stopping it. I tried using SurfaceView or TextureView without success.
I also tried finding a third party video player. I found a commercial one (http://www.vitamio.org/) that I can't really use for business reason. I found an open source one, that hasn't been updated in the last year (https://code.google.com/p/dolphin-player/).
I'm currently targeting Android 4.2 or better on tablet only.
The URL I'm testing with: http://bellvps1.cpl.delvenetworks.com/media/e1b3e24ecb944abd8f4ed823a0b76ddc/68f78d35296243bfb46d2418f03f2fd0/bande-annonce---the-secret-life-of-walter-mitty-1-9efcc5c6e52ac07a3edf84a1b21967995b7796a2.m3u8
This effect can be achieved using a TextureView
.
Essentially what you do is create a MediaPlayer
instance, prepare()
it and start() it, you can then use MediaPlayer.setSurface()
method on any TextureView
to change the surface as the video is playing without any changes to MediaPlayer
object state, as stated in android's docs for setSurface()
method:
This method can be called in any state and calling it does not change the object state.
Please note this implementation is for the sake of demonstration, you should probably use mediaplayer.prepareAsync()
and wait for a callback with onPreparedListener()
, you will also need to set the correct dimensions for the second TextureView
according to your video size, handle orientation changes and of course handle exceptions properly wherever needed.
activity_main.xml:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.view.ViewPager
android:id="@+id/pager"
android:layout_width="300dp"
android:layout_height="300dp"
android:layout_gravity="center"
android:background="#ff0000" />
<Button
android:id="@+id/btn"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:text="Switch to second surface" />
<TextureView
android:id="@+id/tv_full"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
MainActivity.java
public class MainActivity extends Activity {
private static final String VIDEO_URI = "http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4";
private TextureView tvFull;
private MediaPlayer mp;
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mp = new MediaPlayer();
try {
mp.setDataSource(this, Uri.parse(VIDEO_URI));
mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
mp.prepare();
} catch (IOException e) {
e.printStackTrace();
}
((ViewPager) findViewById(R.id.pager)).setAdapter(new PagerAdapter() {
@Override public int getCount() {
return 1;
}
@Override public boolean isViewFromObject(View view, Object o) {
return view == o;
}
@Override public Object instantiateItem(ViewGroup container, int position) {
final TextureView tv = new TextureView(MainActivity.this);
tv.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
@Override public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i2) {
tv.setSurfaceTextureListener(null);
mp.setSurface(new Surface(surfaceTexture));
mp.start();
}
@Override public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int i, int i2) {}
@Override public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
return false;
}
@Override public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {}
});
if (tv.isAvailable()) {
tv.getSurfaceTextureListener().onSurfaceTextureAvailable(tv.getSurfaceTexture(),
tv.getWidth(), tv.getHeight());
}
container.addView(tv, 0);
return tv;
}
@Override public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
}
});
tvFull = (TextureView) findViewById(R.id.tv_full);
findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View view) {
tvFull.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
@Override public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i2) {
tvFull.setSurfaceTextureListener(null);
mp.setSurface(new Surface(surfaceTexture));
}
@Override public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int i, int i2) {
}
@Override public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
return false;
}
@Override public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
}
});
if (tvFull.isAvailable()) {
tvFull.getSurfaceTextureListener().onSurfaceTextureAvailable(tvFull.getSurfaceTexture(),
tvFull.getWidth(), tvFull.getHeight());
}
}
});
}
@Override protected void onDestroy() {
mp.reset();
mp.release();
super.onDestroy();
}
}
I don't know the details of how the video player works, but my hunch is you have to:
Override onSaveInstanceState to save the place in the video (maybe a timestamp?)
Override onRestoreInstanceState to reload the video and seek to the point saved in step 1
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With