Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can VideoView be detach and reattached without stopping the stream?

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.


Note that the ViewPager is not fullscreen. So I can't use LayoutParams to make the video fullscreen. I need to remove the VideoView from the parent in the ViewPager and add it to the root view to be able to show it fullscreen.

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

like image 469
Thierry-Dimitri Roy Avatar asked Oct 31 '13 00:10

Thierry-Dimitri Roy


2 Answers

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();
    }
}
like image 166
woot Avatar answered Oct 28 '22 09:10

woot


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
like image 36
GOLDEE Avatar answered Oct 28 '22 09:10

GOLDEE