31

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

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

  2. ボタン -- 表示される次のアクティビティにユーザーを移動させます。

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

今仮定します

 Total Video length is 60 Minutes

 Current Video progress is 20 Minutes

 Current Buffered progress 30 Minutes 

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

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

問題

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

公式のYoutube Androidアプリでも。同じ問題があります。

編集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 が必要な動作にハッキングできることを確認できます - 表面が破壊された後もバッファリングを保持します。期待どおりに動作する Samsung Galaxy S2 でテストしました。私の場合、新しいアクティビティを開いて戻ると、ビデオ バッファリング (リモート http サーバーからの m4v ビデオのストリーミング) が正常に保持されます。

基本的に、回避策は、独自の VideoView クラスを (ソース コードをコピーして) 作成し、SurfaceHolder.Callback() 実装をハックすることです。VideoView は internal/hide API を使用することに注意してください。そのため、独自のプロジェクトで VideoView のコピーを作成する場合は、inazaruk の記事に従ってinternal/hide API の使用を有効にする必要があります。簡単なハックとして、ここから inazaruk のビルドをダウンロードし、inazaruk-android-sdk-dbd50d4/platforms/android-15-internals/android.jar を使用して、android-sdk/platforms/android-15 の元の android.jar を置き換えます。 /。

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 を使用して同じ効果を達成できるはずです。Interal/Hide API を実行したくない場合は、MediaPlayerActivity を作成します。ApiDemos サンプルの MediaPlayerDemo_Video.java から始めることができます。重要な点は、準備 (結果のバッファリング) とリリース メソッドが SurfaceHolder コールバック メソッドとアクティビティ ライフ サイクル メソッドの両方で適切に処理されていることを確認し、サーフェスが作成/破棄され、アクティビティが開始、再開/一時停止されるたびにビデオを準備/解放しないようにすることです。停止。重要な部分のみを含み、簡単なデモに使用できるダミーの 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 に答える
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 に答える
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 に答える
1

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

于 2012-04-28T22:36:04.087 に答える
1

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

于 2013-12-10T00:58:40.747 に答える
1

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

2 つの可能性があります。

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

楽しむ!:)

于 2012-04-27T21:28:15.307 に答える
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();
}

上記の 2 つのメソッドをアクティビティに追加してみてください。

于 2012-04-25T17:05:55.210 に答える
-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 に答える
-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 に答える