web-dev-qa-db-ja.com

VideoView onResumeがビデオのバッファリングされた部分を失う

私はある活動をしています

  1. VideoView-Webサーバーからビデオをストリーミングします。

  2. ボタン-次のアクティビティに移動します。

アプリケーションが起動すると、Webサーバーからビデオを再生するようにVideoViewが作成されます。

今仮定します

_ Total Video length is 60 Minutes

 Current Video progress is 20 Minutes

 Current Buffered progress 30 Minutes 
_

次に、上記のボタンをクリックすると、ユーザーは次のアクティビティに移動します。

そのアクティビティから[戻る]ボタンを押すと、前のアクティビティ(VideoViewおよびボタン付き)がユーザーの前に表示されます。しかし、再開すると、ビデオのすべてのバッファされた部分が失われるため、VideoViewは最初からビデオの再生を開始しますが、これは本当に悪いことです。 <-実際の問題

問題

アクティビティが再開されると、ビデオのバッファリングされた部分が失われるため、再びバッファリングを開始します。では、ビデオのバッファリングされた部分の再バッファリングを克服するにはどうすればよいですか?

公式のYouTube Android app。でも同じ問題があります。

編集1:

アクティビティで以下のコードを試しましたが、機能しません。

_@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()メソッドにその位置を使用して、その期間にビデオをシークしました。これは正常に動作します。ただし、ビデオのバッファリングは、シーク位置からビデオを開始する最初から開始されます。

どんな助けでも深く感謝します。

30
Kartik Domadiya

元のVideoViewソースコードをハッキングするのに数時間費やしましたが、VideoViewをハッキングして動作を確認できます-表面が破壊された後もバッファリングを保持します。 Samsung Galaxy S2でテストしましたが、期待どおりに機能します。私の場合、新しいアクティビティを開いて戻ったときに、ビデオバッファリング(リモートhttpサーバーからのm4vビデオのストリーミング)は正常に保持されます。

基本的には、回避策は(ソースコードをコピーして)独自のVideoViewクラスを作成し、SurfaceHolder.Callback()実装をハックすることです。 VideoViewは内部/非表示APIを使用しているため、独自のプロジェクトでVideoViewのコピーを作成する場合は、内部/非表示APIの使用を有効にするために inazarukの記事 に従う必要があります。簡単なハックとして、私は here からinazarukのビルドをダウンロードし、inazaruk-Android-sdk-dbd50d4/platforms/Android-15-internals/Android.jarを使用して私のAndroid-の元のAndroid.jarを置き換えます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クラスを適切に設計および実装する必要があります。

更新:

別の方法として、interal/hide APIを実行したくない場合は、単純なMediaPlayerを使用してMediaPlayerActivityを作成し、同じ効果を実現できるはずです。ApiDemosサンプルのMediaPlayerDemo_Video.Javaから始めることができます。重要な点は、準備(結果のバッファリング)とリリースメソッドがSurfaceHolderコールバックメソッドとアクティビティライフサイクルメソッドの両方で適切に処理され、サーフェスが作成/破棄され、アクティビティが開始、再開/一時停止されるたびに準備/リリースビデオを回避することです。停止。ダミーの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>
24
yorkw

私はそれを修正する解決策を見つけました:

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

それは私のために働く、私はそれがあなたを助けると確信しています

3
Khang .NT

ビデオビューがバックグラウンドに移動するとバッファが失われるため(可視性の変更)、onWindowVisibilityChangedVideoViewメソッドをオーバーライドして、この動作をブロックしてみてください。ビデオビューが表示されるようになる場合にのみ、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);
        }
    }
}
2
Ronnie

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) {
                }
}
2
voidRy

videoView.resume()onResume()の問題は、ここで確認できます: VideoView.resume()VideoView.resume()openVideo()を呼び出し、最初に以前のMediaPlayerインスタンスを解放してから、新しいインスタンスを開始します。これからの簡単な方法はわかりません。

私は2つの可能性を考えています。

  • MediaPlayerインスタンスを必要な期間保持する独自のVideoViewを記述します。または、ソースを取得して好みに合わせて変更します。これはオープンソースです(ただし、ライセンスを確認してください)。
  • VideoViewとWebサーバーの間に立つネットワークプロキシをアプリに作成します。プロキシをWebサーバーに向け、VideoViewをプロキシに向けます。プロキシはデータのダウンロードを開始し、後で使用できるように継続的に保存し、リスニングMediaPlayer(VideoViewによって開始された)に渡します。 MediaPlayerの接続が切断されても、ダウンロード済みのデータは保持されるため、MediaPlayerが再生を再開したときに、再度ダウンロードする必要はありません。

楽しんで! :)

1
Szabolcs Berecz

カスタムのVideoViewや手動による構成変更の処理を必要としないバージョンを作成しました。説明については、 バッファリングされたビデオによるAndroid VideoViewの向きの変更 を参照してください。

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

あなたの活動に上記の2つの方法を追加してみてください。

0
Shankar Agarwal

2つの個別の問題について説明しましたが、バッファリングされたビデオを保持する方法はわかりませんが、onPauseで getCurrentPosition を呼び出すと、最初から開始することを回避できます。 seekTo ononResume。この呼び出しは非同期ですが、部分的な解決策が得られる可能性があります。

0
MByD