【问题标题】:VideoView onResume loses buffered portion of the videoVideoView onResume 丢失视频的缓冲部分
【发布时间】:2012-04-03 04:17:51
【问题描述】:

我有一个活动,其中有

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

  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() 方法中保存了视频的当前播放位置,我已经使用该位置将视频搜索到该持续时间。这工作正常。但是视频缓冲从头开始,它从搜索位置开始视频。

非常感谢任何帮助。

【问题讨论】:

  • videoView 中未使用 onPause /onResume 方法中使用的内置方法尝试此代码
  • 这是几个月前提出的重复问题..但仍未得到答复..stackoverflow.com/q/8400680/857361
  • 我认为这个问题是这个Question的副本
  • 老实说,Android 中的 VideoView 有很多不足之处。我抓住了源代码并根据需要对其进行了修改(例如删除 VideoView 用于在视频开始时停止所有音频的代码)。
  • @Kartik 你如何解决问题你能分享答案/

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


【解决方案1】:

我花了几个小时试图破解原始 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 中的原始 android.jar -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,但是,您可以从 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>

【讨论】:

  • 鉴于它是可行的,请参阅我的更新以了解替代方法。
  • 我试过了,但它没有加载视频:(。我可能遗漏了一些东西。
  • 我在第二种方法上做了一些 POC,见附件源代码。
  • 我会尝试并让您知道...非常感谢您在此问题上投入重要时间..非常感谢..
  • 干得好,先生。 +2。您是如何获得没有复选标记的赏金的?!
【解决方案2】:

我找到了解决方法:

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

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

【讨论】:

  • 我在这里遇到了 IllegalStateException
【解决方案3】:

当视频视图进入后台时缓冲区丢失(可见性改变),您应该尝试通过覆盖VideoViewonWindowVisibilityChanged 方法来阻止此行为。仅当视频视图变得可见时才调用 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);
        }
    }
}

【讨论】:

  • 对不起,我无法理解您的意思?你能详细说明一下吗?
  • 覆盖onWindowVisibilityChanged 可能会在您转到B 时阻止表面视图破坏表面。VideoViewSurfaceView 的扩展...在此处查看其代码..@987654321 @
  • 您可以尝试通过静态引用 VideoView.. 不是一个好主意,但看看这是否可以防止 videoview 被破坏..
  • 只是一个小测试。不要暂停或暂停。在您进行下一个活动并返回时让视频播放.. 并删除 resume cal.. 看看缓冲区是否仍然存在.. 让我们知道会发生什么..
【解决方案4】:

你试过 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) {
                }
}

【讨论】:

  • 这就是我所做的。但它会清除缓冲部分。
【解决方案5】:

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

我看到了两种可能性:

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

玩得开心! :)

【讨论】:

    【解决方案6】:

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

    【讨论】:

      【解决方案7】:

      我制定了一个不需要自定义 VideoView 或手动配置更改处理的版本。请参阅Android VideoView orientation change with buffered video 了解说明。

      【讨论】:

      • 这听起来很棒。我正在使用 API 14,所以我必须看看是否有合理的方法可以让它在那么远的地方工作。我将在本周晚些时候对其进行测试。
      【解决方案8】:
      @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();
      }
      

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

      【讨论】:

      • videoView.pause() & videoViewresume() 必须在 super 之前而不是之后
      【解决方案9】:
      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();
          }
      

      【讨论】:

      • 感谢您的回复。这是我已经做过的。但是当从其他活动恢复时,视频的缓冲部分会丢失,因此视频会从头开始缓冲。
      • 你可以尝试使用mediaPlay,在这个使用onPause/onResume方法很好
      【解决方案10】:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-04-23
        • 1970-01-01
        • 2012-01-09
        • 2016-11-20
        • 1970-01-01
        • 2017-08-26
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多