VideoView onResume 丢失了视频的缓冲部分 [英] VideoView onResume loses buffered portion of the video

查看:29
本文介绍了VideoView onResume 丢失了视频的缓冲部分的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个活动,其中有

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

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

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

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

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

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

现在假设

 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.

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

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

问题

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

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 ?

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

Even official Youtube android app. has the same problem.

编辑 1 :

我在 Activity 中尝试了以下代码,但它不起作用.

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 ?

当前的解决方法

我已在 onPause() 方法和 onResume() 方法中保存了视频的当前播放位置,我已使用该位置将视频搜索到该持续时间.这工作正常.但是视频缓冲从头开始,它从搜索位置开始播放视频.

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.

非常感谢任何帮助.

推荐答案

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

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.

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

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 源代码可从 GrepCode.成功创建自己的副本且没有编译错误后,将 SurfaceHolder.Callback() 更改为如下所示:

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

并确保您不要像这样在 MediaPlayerActivity 中显式调用 videoView.resume()、videoView.setVideoURI()、videoView.suspend() 和 videoView.stopPlayback():

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

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

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.

更新:

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

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:

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:

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>

这篇关于VideoView onResume 丢失了视频的缓冲部分的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆