VideoView onResume теряет буферизованную часть видео - PullRequest
29 голосов
/ 03 апреля 2012

У меня есть активность, в которой есть

  1. VideoView - потоковое видео с веб-сервера.

  2. Кнопка - принимаетпользователь к следующей активности, которая будет показана.

Когда приложение запускается, VideoView запускается для воспроизведения видео с веб-сервера.

Теперь предположим,

 Total Video length is 60 Minutes

 Current Video progress is 20 Minutes

 Current Buffered progress 30 Minutes 

Теперь, когда я нажимаю навышеупомянутая кнопка, которая ведет пользователя к следующему действию.

Из этого действия, если я нажимаю кнопку «Назад», перед пользователем появляется «Предыдущее действие» (с VideoView и Button).Но при возобновлении все буферизованная часть видео теряется, и, следовательно, VideoView начинает воспроизводить видео с самого начала, что действительно плохо. <- Актуальная проблема </strong>

Проблема

Когда действие возобновляется, буферизованная часть видео теряется и, следовательно, начинает буферизовать его снова.Так как же преодолеть повторную буферизацию буферизованной части видео?

Даже официальное приложение для Android на 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() Я использовал эту позицию для поиска видеок этой продолжительности.Это отлично работает.Но буферизация видео начинается с начала, то есть начинается с позиции поиска.

Любая помощь очень важна.

Ответы [ 10 ]

23 голосов
/ 29 апреля 2012

Я потратил несколько часов, пытаясь взломать исходный исходный код VideoView, и теперь я могу подтвердить, что VideoView можно взломать, чтобы он вел себя так, как вам нужно - сохраняя буферизацию после разрушения поверхности.Я протестировал на своем Samsung Galaxy S2, который работает, как и ожидалось, в моем случае буферизация видео (потоковое видео m4v с удаленного http-сервера) успешно сохраняется, когда я открываю новое действие и возвращаюсь назад.

По сути, обходной путь - создать собственный класс VideoView (путем копирования исходного кода) и взломать реализацию SurfaceHolder.Callback ().Имейте в виду, что VideoView использует некоторый внутренний / скрытый API, поэтому, если вы хотите создать копию VideoView в своем собственном проекте, вы должны следовать статье inazaruk , чтобы включить использование внутреннего / скрытого API.Для быстрого взлома я просто скачиваю сборку Иназарука с здесь и использую inazaruk-android-sdk-dbd50d4 / platform / 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.
    }
};

И убедитесь, что вы не вызываете videoView.resume (), videoView.setVideoURI (), videoView.suspend () и videoView.stopPlayback () явно в вашей MediaPlayerActivity, как это:

@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. Вы можете начать с MediaPlayerDemo_Video.java в примере ApiDemos.Ключевым моментом является то, чтобы убедиться, что подготовка (буферизация результатов) и метод освобождения должным образом обрабатываются как в методах обратного вызова 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>
3 голосов
/ 13 августа 2015

Я нашел решение исправить это:

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

Это работает для меня, я уверен, что это поможет вам

2 голосов
/ 01 мая 2012

Вы пробовали 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 голосов
/ 26 апреля 2012

Поскольку буфер теряется, когда просмотр видео переходит на задний план (изменение видимости), вы должны попытаться заблокировать это поведение, переопределив onWindowVisibilityChanged метод VideoView.Вызывайте супер, только если просмотр видео становится видимым.Может иметь побочные эффекты.

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);
        }
    }
}
1 голос
/ 10 декабря 2013

Я разработал версию, которая не требует пользовательского VideoView или ручной обработки изменений конфигурации. См. Android VideoView изменение ориентации с буферизованным видео для объяснения.

1 голос
/ 28 апреля 2012

Проблема с videoView.resume() в onResume() может быть замечена здесь: VideoView.resume () . VideoView.resume() вызывает openVideo(), который сначала освобождает все предыдущие экземпляры MediaPlayer, а затем запускает новый. Я не вижу простого выхода из этого.

Я вижу две возможности:

  • Напишите свой собственный VideoView, который сохраняет экземпляр MediaPlayer для столько, сколько вы хотите. Или просто взять исходный код и изменить по своему вкусу, это открытый исходный код (проверьте лицензию).
  • Создайте сетевой прокси в вашем приложении, который стоит между VideoView и веб-сервер. Вы указываете свой прокси на веб-сервере и VideoView для прокси. Прокси начинает загрузку данных, сохраняет их постоянно для последующего использования и передает его прослушивающему MediaPlayer (который был запущен VideoView). Когда MediaPlayer отключается, вы продолжаете уже загруженные данные, так что когда MediaPlayer перезапускает воспроизведение, вам не нужно загружать его снова.

Веселись! :)

0 голосов
/ 29 апреля 2012

Вы упомянули две отдельные проблемы, и хотя я не знаю, как сохранить буферизованное видео, вы все равно можете не начинать с самого начала, вызвав getCurrentPosition в onPause и seekTo onResume. Этот вызов асинхронный, но он может дать вам частичное решение.

0 голосов
/ 25 апреля 2012
@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();
}

попробуйте добавить вышеупомянутые два метода в вашу деятельность.

0 голосов
/ 03 апреля 2012

В функции 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();
}
0 голосов
/ 03 апреля 2012
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();
    }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...