VideoView onResume丢失了视频的缓冲部分

时间:2012-04-03 04:17:51

标签: android video-streaming android-videoview android-video-player

我有一个活动,其中有

  1. VideoView     - 从网络服务器流式传输视频。

  2. 按钮 -    使用户进入下一个要显示的活动。

  3. 申请开始时,    VideoView用于从网络服务器播放视频。

    现在假设

     Total Video length is 60 Minutes
    
     Current Video progress is 20 Minutes
    
     Current Buffered progress 30 Minutes 
    

    现在,当我点击上面提到的Button时,会将用户带到下一个活动。

    如果按下后退按钮,则从该活动开始,用户前面会显示“上一个活动”(带有VideoView和按钮)。 但是当恢复时,视频的所有缓冲部分都会丢失,因此VideoView从一开始就播放视频,这真的很糟糕。 < - 实际问题

    问题

    当Activity重新恢复时,视频的缓冲部分会丢失,因此会再次开始缓冲。那么如何克服重新缓冲视频的缓冲部分呢?

    甚至官方的Youtube安卓应用。有同样的问题。

    修改1:

    我在Activity中尝试了以下代码,但它无效。

    @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();
    }
    

    任何人都可以指导我这个问题吗?或者我错过了一些能使这项工作完美的东西?

    当前解决方法

    我已使用onPause()方法和onResume()方法保存了视频的当前播放位置,我已使用该位置来查找该持续时间的视频。这很好用。但视频缓冲从一开始就从搜索位置开始播放视频。

    非常感谢任何帮助。

10 个答案:

答案 0 :(得分:23)

我花了几个小时试图破解原始的VideoView源代码,现在我可以确认VideoView可以被黑客攻击你想要的行为 - 在表面被破坏后保留缓冲。我已经在三星Galaxy S2上进行了测试,按照预期工作,在我的情况下,当我打开一个新活动并返回时,视频缓冲(来自远程http服务器的流式m4v视频)成功保留。

基本上,解决方法是创建您自己的VideoView类(通过复制源代码),并破解SurfaceHolder.Callback()实现。请记住,VideoView使用一些内部/隐藏API,因此如果您想在自己的项目中创建VideoView的副本,则必须按照inazaruk's article启用内部/隐藏API。作为一个快速黑客,我只是从here下载inazaruk的构建并使用inazaruk-android-sdk-dbd50d4 / platforms / android-15-internals / android.jar替换我在android-sdk / platforms中的原始android.jar /机器人-15 /.

可以从GrepCode下载VideoView源代码。成功创建自己的副本而没有编译错误后,将SurfaceHolder.Callback()更改为以下内容:

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.
    }
};

并确保不要在MediaPlayerActivity中明确调用videoView.resume(),videoView.setVideoURI(),videoView.suspend()和videoView.stopPlayback(),如下所示:

@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();
}

请注意,我刚刚做了一个脏黑客来证明可行性,你应该正确设计和实现你的VideoView类,以避免任何副作用。

<强>更新

作为替代方案,如果您不想执行内部/隐藏API内容,则应该能够使用普通MediaPlayer创建MediaPlayerActivity来实现相同效果您可以从ApiDemos示例中的MediaPlayerDemo_Video.java开始。关键是确保在SurfaceHolder Callback方法和Activity生命周期方法中正确处理prepare(结果缓冲)和release方法,以避免每次创建/销毁表面时准备/释放视频,并且Activity已启动,恢复/暂停,停止。我已经创建了一个虚拟BufferedMediaPlayerActivity(在此处发布的高度简化),它只包含关键部分并可用于快速演示,它没有MediaController,但是,您可以从Logcat检查以查看缓冲区百分比是否实际保留每次打开新活动并返回时,增加而不是从0滚动。

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>

答案 1 :(得分:3)

我找到了解决方法:

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

它适合我,我相信它可以帮助你

答案 2 :(得分:2)

当视频视图转到后台(可见性发生变化)时缓冲区丢失,您应该尝试通过覆盖onWindowVisibilityChanged VideoView方法来阻止此行为。仅在视频视图变得可见时才调用super。可能有副作用。

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

答案 3 :(得分:2)

您是否尝试过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) {
                }
}

答案 4 :(得分:1)

videoView.resume()onResume()的问题可以在这里看到:VideoView.resume()VideoView.resume()调用openVideo(),它首先释放所有以前的MediaPlayer实例,然后启动一个新实例。我看不出一个简单的方法。

我看到两种可能性:

  • 编写自己的VideoView,保留MediaPlayer实例 只要你想要。或者只是根据自己的喜好选择源代码, 它是开源的(但检查许可证)。
  • 在您的应用中创建一个位于VideoView和之间的网络代理 网络服务器。您将代理指向Web服务器和 VideoView到代理。代理开始下载数据并保存 连续供以后使用并将其传递给收听的MediaPlayer(这是 由VideoView开始)。当MediaPlayer断开连接时,请保留 已下载的数据,以便在MediaPlayer重新启动时 播放时,您无需再次下载。

玩得开心! :)

答案 5 :(得分:1)

我已经找到了一个不需要自定义VideoView或手动配置更改处理的版本。有关说明,请参阅Android VideoView orientation change with buffered video

答案 6 :(得分: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();
}

尝试在您的活动中添加以上两种方法。

答案 7 :(得分:0)

你提到了两个不同的问题,虽然我不知道如何保留缓冲的视频,你仍然可以通过调用onPause中的getCurrentPosition和onResume上的seekTo来避免从头开始。此调用是异步的,但它可能会为您提供部分解决方案。

答案 8 :(得分:-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();
    }

答案 9 :(得分:-2)

在onPause()函数中,而不是

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

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