VideoView onResume loses buffered portion of the video
Asked Answered
I

10

31

I am having an Activity in which there is

  1. VideoView -- Streams a video from a webserver.

  2. Button -- Takes the user to the next activity to be shown.

When the application starts, VideoView is made to play the Video from a webserver.

Now assume

 Total Video length is 60 Minutes

 Current Video progress is 20 Minutes

 Current Buffered progress 30 Minutes 

Now when I click on the above mentioned Button which takes user to the next activity.

From that Activity if i press the back button, Previous Activity(with VideoView and Button) appears in front of the user. But when resumed all the Buffered Portion of the video is lost and hence the VideoView starts playing the video from the beginning which is really bad. <-- Actual Problem

Problem

When Activity is resumed back, the buffered portion of the video is lost and hence starts buffering it again. So how to overcome re-buffering the buffered portion of the Video ?

Even official Youtube android app. has the same problem.

Edit 1 :

I tried the below code in Activity but its not working.

@Override
protected void onPause() {
    // TODO Auto-generated method stub
    super.onPause();
    videoView.suspend();
}

@Override
protected void onResume() {
    // TODO Auto-generated method stub
    super.onResume();
    videoView.resume();
}

Can anyone guide me regarding this problem ?. Or am I missing something to make this work perfectly ?

Current Workaround

I have saved the current playing position of the video in onPause() method and in onResume() method I have used that position to seek the video to that duration. This works fine. But the video buffering starts from the beginning tho it starts the video from the seek position.

Any help is deeply appreciated.

Irrelative answered 3/4, 2012 at 4:17 Comment(5)
videoView in not used onPause /onResume method in inbuilt method used try this codePhatic
Here is a duplicate question asked a few months ago.. but still unanswered.. stackoverflow.com/q/8400680/857361Hardpressed
I think this question is a duplicate of this QuestionVshaped
Honestly, VideoView in Android has a lot of shortcomings. I grabbed the source and modified it to work as I needed (such as removing the code that VideoView uses to stop all audio when a video starts).Manufacture
@Kartik how u reolved issue can u pls share answer /Irresolute
M
25

I've spent several hours trying to hack the original VideoView source code and Now I can confirm VideoView can be hacked to behavior what you want - retain buffering after surface destroyed. I've tested on my Samsung Galaxy S2, which works as expected, in my case, the video buffering (streaming m4v video from remote http server) is successfully retained when I open a new activity and go back.

Basically, the workaround is create you own VideoView class (by copying the source code), and hack the SurfaceHolder.Callback() implementation. Bear in mind that VideoView use some internal/hide API, so if you want to create a copy of VideoView in your own project, you have to follow the inazaruk's article to enable using internal/hide API. As a quick hack, I just download inazaruk's build from here and use inazaruk-android-sdk-dbd50d4/platforms/android-15-internals/android.jar replace my original android.jar in my android-sdk/platforms/android-15/.

VideoView source code can be downloaded from GrepCode. Once you successfully create you own copy without compile error, change SurfaceHolder.Callback() to something like this:

private boolean videoOpened = false;

SurfaceHolder.Callback mSHCallback = new SurfaceHolder.Callback()
{

    ... ...

    public void surfaceCreated(SurfaceHolder holder)
    {
        Log.i(TAG, "---------------------> surface created.");
        mSurfaceHolder = holder;
        if (!videoOpened) {
          openVideo(); // <-- if first time opened, do something as usual, video is buffered.
          /** 
           * openVideo() actually mMediaPlayer.prepareAsync() is the first key point, it is
           * also called in other two VideoView's public methods setVideoURI() and resume(), 
           * make sure you don't call them in your activity.
           */ 
          videoOpened = true;
        } else {
          start();  // <-- if back from another activity, simply start it again.
        }
    }

    public void surfaceDestroyed(SurfaceHolder holder)
    {
        Log.i(TAG, "---------------------> surface destroyed.");
        // after we return from this we can't use the surface any more.
        mSurfaceHolder = null;
        if (mMediaController != null) mMediaController.hide();
        //release(true);
        /**
         * release() actually mMediaPlayer.release() is the second key point, it is also
         * called in other two VideoView's public methods stopPlayback() and suspend(), make
         * sure you don't call them in your activity.
         */
        pause(); // <-- don't release, just pause.
    }
};

And make sure you don't call videoView.resume(), videoView.setVideoURI(), videoView.suspend() and videoView.stopPlayback() explicitly in you MediaPlayerActivity like this:

@Override
protected void onResume() {
  if (videoView != null)
    videoView.resume();  // <-- this will cause re-buffer.
    super.onResume();
}

@Override
protected void onPause() {
  if (videoView != null)
    videoView.suspend(); // <-- this will cause clear buffer.
    super.onPause();
}

Note that I have just done a dirty hack to prove the feasibility, You should design and implement your VideoView class properly to avoid any side-effect.

Update:

As an alternative, you should able to achieve same effect using plain MediaPlayer create your MediaPlayerActivity if you don't want to do the interal/hide API stuff You can start with the MediaPlayerDemo_Video.java in ApiDemos sample. The key point is make sure prepare (result buffering) and release method is properly handled in both SurfaceHolder Callback methods and Activity life cycle method to avoid prepare/release video every time surface is created/destroyed, and Activity is started,resumed/paused,stopped. I've created a dummy BufferedMediaPlayerActivity (highly simplified for posting here) which contains only key parts and can be used for quick demonstration, it doesn't have MediaController, however, you can check from Logcat to see that the buffer percentage is actually keep increasing instead of rolling over from 0 every time you open new activity and go back.

BufferedMediaPlayerActivity.java:

package com.example;

import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnBufferingUpdateListener;
import android.media.MediaPlayer.OnPreparedListener;
import android.os.Bundle;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class BufferedMediaPlayerActivity extends Activity implements OnPreparedListener, OnBufferingUpdateListener, SurfaceHolder.Callback {

  private static final String TAG = "BufferedMediaPlayerActivity";
  private int mVideoWidth;
  private int mVideoHeight;
  private MediaPlayer mMediaPlayer;
  private SurfaceView mPreview;
  private SurfaceHolder holder;
  private String path;
  private boolean mIsVideoReadyToBePlayed = false;

  @Override
  public void onCreate(Bundle icicle) {
    super.onCreate(icicle);
    setContentView(R.layout.buffered_media_player);
    mPreview = (SurfaceView) findViewById(R.id.surface);
    holder = mPreview.getHolder();
    holder.addCallback(this);
    holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    holder.setFixedSize(mVideoWidth, mVideoHeight);
    // retrieve httpUrl passed from previous activity.
    path = getIntent().getExtras().getString("videoUrl");
  }

  @Override
  public void onDestroy() {
    super.onDestroy();
    if (mMediaPlayer != null) {
      mMediaPlayer.release();
      mMediaPlayer = null;
    }
    mIsVideoReadyToBePlayed = false;
  }

  private void playVideo() {
    mIsVideoReadyToBePlayed = false;
    try {
      // Create a new media player and set the listeners
      mMediaPlayer = new MediaPlayer();
      mMediaPlayer.setDataSource(path);
      mMediaPlayer.setDisplay(holder);
      mMediaPlayer.prepare();
      mMediaPlayer.setOnPreparedListener(this);
      mMediaPlayer.setOnBufferingUpdateListener(this);
      mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
    } catch (Exception e) {
      Log.e(TAG, "error: " + e.getMessage(), e);
    }
  }

  @Override
  public void onPrepared(MediaPlayer mediaplayer) {
    Log.d(TAG, "onPrepared called");
    mIsVideoReadyToBePlayed = true;
    if (mIsVideoReadyToBePlayed) {
      mMediaPlayer.start();
    }
  }

  @Override
  public void onBufferingUpdate(MediaPlayer mp, int percent) {
    Log.i(TAG, "---------------> " + percent);
  }

  @Override
  public void surfaceChanged(SurfaceHolder surfaceholder, int i, int j, int k) {
    Log.d(TAG, "surfaceChanged called");
  }

  @Override
  public void surfaceCreated(SurfaceHolder holder) {
    Log.d(TAG, "surfaceCreated called");
    if (!mIsVideoReadyToBePlayed)
      playVideo();
    else
      mMediaPlayer.start();
  }

  @Override
  public void surfaceDestroyed(SurfaceHolder surfaceholder) {
    Log.d(TAG, "surfaceDestroyed called");
    mMediaPlayer.pause();
  }

}

buffered_media_player.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical"
  android:layout_width="match_parent"
  android:layout_height="match_parent">

  <SurfaceView android:id="@+id/surface"
    android:layout_width="200dip"
    android:layout_height="160dip"
    android:layout_gravity="center">
  </SurfaceView>

</LinearLayout>
Medullated answered 28/4, 2012 at 22:26 Comment(7)
Given the fact that it is feasible, see my update for the alternative approach.Medullated
I tried this but it didnot load the video :(. I may be missing something.Irrelative
I have done some POC on second approach, see the attached source code.Medullated
I will try it and let you know... Thanks a ton for investing your important time in this issue.. Thanks alot..Irrelative
Good work, sir. +2. How did you get a bounty with no checkmark?!Wende
@Wende I finally figured out how to do this without a hacked VideoView. See my answer below.Recrystallize
@Medullated can u pls help me how will i handle issue i m also facing same issue once i rotate the device now its starts bufferingIrresolute
D
6

I found a solution fix it:

VideoView videoView;
MediaPlayer mp;

videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
            @Override
            public void onPrepared(MediaPlayer mp) {
                this.mp = mp;
            }
        });

public void pause(){
    //NOT videoview.pause();
    if (mp != null){
       mp.pause();
    }
}

public void resume(){
    //NOT videoview.resume();
    if (mp != null){
       mp.start();
    }   
}

It work for me, i sure it help you

Derwent answered 13/8, 2015 at 1:26 Comment(1)
I'm getting IllegalStateException hereTurnip
H
2

As the buffer is lost when the video view goes to the background(change in visibility), you should try blocking this behavior by overriding the onWindowVisibilityChanged method of VideoView. Call super only if the video view is becoming visible. May have side-effects.

public class VideoTest extends VideoView {

    public VideoTest(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onWindowVisibilityChanged(int visibility) {
        if (visibility == View.VISIBLE) { 
            super.onWindowVisibilityChanged(visibility);
        }
    }
}
Hardpressed answered 26/4, 2012 at 11:12 Comment(4)
Sorry I am not able to get your point ?. Can you please elaborate more ?Irrelative
overriding onWindowVisibilityChanged might block the surface view from destroying the surface when you go to B.. VideoView is extension of SurfaceView... See its code here.. grepcode.com/file/repository.grepcode.com/java/ext/…Hardpressed
You can try by having a static reference to the VideoView.. not a goodidea but see if that prevents the videoview from getting destroyed..Hardpressed
Just a small test. Dont pause or suspend. Let the video play while you go to next activity and come back.. and remove resume cal too.. See whether the buffer remains.. Let us know what happens..Hardpressed
N
2

Have you tried seekto()

@Override
protected void onResume() {
    super.onResume();
    try{
        if (video_view != null) {
            video_view.seekTo(position);    
            video_view.start();
        }
    }catch (Exception e) {
                }
}

@Override
protected void onPause() {
    super.onPause();    
    try{
        if (video_view != null) {
            position = video_view.getCurrentPosition();
            video_view.pause();         
        }
    }catch (Exception e) {
                }
}
Neuromuscular answered 1/5, 2012 at 14:3 Comment(1)
That is what i have done. But it wipes out the buffered portion.Irrelative
A
1

The problem with videoView.resume() in onResume() can be seen here: VideoView.resume(). VideoView.resume() calls openVideo() which first releases any previous MediaPlayer instances and then starts a new one. I cannot see an easy way out of this.

I see two possibilities:

  • Write your own VideoView which retains the MediaPlayer instance for as long as you want. Or just take the source and modify to your liking, it's open source (check the license, though).
  • Create a network proxy in your app which stands between the VideoView and the web server. You point your proxy to the web server and the VideoView to the proxy. The proxy starts downloading the data, saves it continuously for later use and passes it to the listening MediaPlayer (which was started by the VideoView). When the MediaPlayer disconnects, you keep the already downloaded data, so that when the MediaPlayer restarts the playback, you don't have to download it again.

Have fun! :)

Actinolite answered 27/4, 2012 at 21:28 Comment(0)
A
1

You've mentioned two separate problems, and while I don't know how to keep the buffered video, You can still avoid starting from the beginning by calling getCurrentPosition in onPause and seekTo on onResume. This call is asynchronous, but it might give you a partial solution.

Arid answered 28/4, 2012 at 22:36 Comment(0)
R
1

I've worked out a version that does not require a custom VideoView or manual configuration change handling. See Android VideoView orientation change with buffered video for an explanation.

Recrystallize answered 10/12, 2013 at 0:58 Comment(1)
This sounds great. I was working with API 14, so I'll have to see if there's a reasonable way to make it work that far back. I'll test it out later in the week.Wende
M
0
@Override
protected void onPause() {
    // TODO Auto-generated method stub
    videoView.pause();
    super.onPause();
}

@Override
protected void onRestart() {
    // TODO Auto-generated method stub
    videoView.resume();
    super.onPause();
}

try by adding above twomethods in your activity.

Mousebird answered 25/4, 2012 at 17:5 Comment(1)
videoView.pause() & videoViewresume() must be before super not afeter generally thatMousebird
P
-2
public class Video_play extends Activity {
    VideoView vv;
   String URL;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onCreate(savedInstanceState);
        setContentView(R.layout.play_video);
        URL= getIntent().getStringExtra("URL");

        vv=(VideoView)findViewById(R.id.videoView1);
        MediaController mediaController = new MediaController(this);
        mediaController.setAnchorView(vv);
        Log.v("URL",URL);


//       Uri uri = Uri.parse(URL);
//        vv.setVideoURI(uri);
        vv.setMediaController(new MediaController(this));
        vv.setVideoPath(URL);

//        vv.requestFocus();
//       
//        vv.start();


//      Uri uri=Uri.parse(URL);
//
//    
//      vv.setVideoURI(uri);
        vv.start();
    }
Phatic answered 3/4, 2012 at 4:22 Comment(2)
Thanks for the reply. This is what i have already done. But when resuming from other activity, the buffered portion of the video is lost and hence video starts buffering from the beginning.Irrelative
hii you can try mediaPlay use,in this used onPause/onResume method nicelyPhatic
F
-2

In onPause() function, instead of

@Override
protected void onPause() {
    // TODO Auto-generated method stub
    super.onPause();
    videoView.suspend();
}

try

@Override
protected void onPause() {
    // TODO Auto-generated method stub
    super.onPause();
    videoView.pause();
}
Forejudge answered 3/4, 2012 at 4:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.