31

我有一个活动,其中有

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

  2. 按钮——将用户带到下一个要显示的活动。

当应用程序启动时,VideoView 会播放来自网络服务器的视频。

现在假设

 Total Video length is 60 Minutes

 Current Video progress is 20 Minutes

 Current Buffered progress 30 Minutes 

现在,当我单击上面提到的按钮时,它会将用户带到下一个活动。

如果我按下后退按钮,则从该活动中,上一个活动(带有 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()方法中我使用该位置将视频搜索到该持续时间。这工作正常。但是视频缓冲从头开始,它从搜索位置开始视频。

任何帮助都深表感谢。

4

10 回答 10

25

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

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

VideoView 源代码可以从GrepCode下载。成功创建自己的副本且没有编译错误后,将 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 类以避免任何副作用。

更新:

作为替代方案,您应该能够使用普通的 MediaPlayer 创建您的 MediaPlayerActivity 来实现相同的效果,如果您不想执行 interal/hide API 的东西您可以从 ApiDemos 示例中的 MediaPlayerDemo_Video.java 开始。关键是确保在 SurfaceHolder 回调方法和 Activity 生命周期方法中正确处理准备(结果缓冲)和释放方法,以避免每次创建/销毁表面以及启动、恢复/暂停活动时准备/释放视频,停了下来。我创建了一个虚拟的 BufferedMediaPlayerActivity (高度简化,以便在此处发布),它只包含关键部分,可用于快速演示,但是它没有 MediaController,

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>
于 2012-04-28T22:26:24.280 回答
5

我找到了一个解决方案:

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

它对我有用,我相信它可以帮助你

于 2015-08-13T01:26:54.373 回答
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) {
                }
}
于 2012-05-01T14:03:03.940 回答
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);
        }
    }
}
于 2012-04-26T11:12:42.547 回答
1

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

我看到两种可能性:

  • 编写您自己的 VideoView,它可以根据需要保留 MediaPlayer 实例。或者只是获取源代码并根据自己的喜好进行修改,它是开源的(不过请检查许可证)。
  • 在您的应用程序中创建一个位于 VideoView 和 Web 服务器之间的网络代理。您将代理指向 Web 服务器,将 VideoView 指向代理。代理开始下载数据,不断保存以备后用,并将其传递给正在监听的 MediaPlayer(由 VideoView 启动)。当 MediaPlayer 断开连接时,您保留已经下载的数据,这样当 MediaPlayer 重新开始播放时,您不必再次下载。

玩得开心!:)

于 2012-04-27T21:28:15.307 回答
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.

于 2012-04-28T22:36:04.087 回答
1

我制定了一个不需要自定义 VideoView 或手动配置更改处理的版本。有关说明,请参阅使用缓冲视频的 Android VideoView 方向更改

于 2013-12-10T00:58:40.747 回答
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();
}

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

于 2012-04-25T17:05:55.210 回答
-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();
}
于 2012-04-03T04:43:58.903 回答
-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();
    }
于 2012-04-03T04:22:18.960 回答