VideoView onResume теряет буферизованную часть видео

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

  1. VideoView — транслирует видео с веб-сервера.

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

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

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

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

Любая помощь приветствуется.


person Kartik Domadiya    schedule 03.04.2012    source источник
comment
videoView не используется метод onPause/onResume во встроенном методе, который используется, попробуйте этот код   -  person Android    schedule 03.04.2012
comment
Вот повторяющийся вопрос, заданный несколько месяцев назад... но до сих пор без ответа... stackoverflow.com/q/8400680/857361   -  person Ronnie    schedule 26.04.2012
comment
Я думаю, что этот вопрос является дубликатом этого Question   -  person Renard    schedule 26.04.2012
comment
Честно говоря, у VideoView в Android много недостатков. Я взял исходный код и модифицировал его, чтобы он работал так, как мне было нужно (например, удалив код, который VideoView использует для остановки звука при запуске видео).   -  person Phil    schedule 30.04.2012
comment
@Kartik, как ты решил проблему, можешь поделиться ответом, пожалуйста /   -  person Erum    schedule 17.09.2015


Ответы (10)


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

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

}

буферизованный_медиа_плеер.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>
person yorkw    schedule 28.04.2012
comment
Учитывая тот факт, что это возможно, см. мое обновление для альтернативного подхода. - person yorkw; 30.04.2012
comment
Я попробовал это, но видео не загрузилось :(. Возможно, я что-то упустил. - person Kartik Domadiya; 02.05.2012
comment
Я сделал несколько POC для второго подхода, см. прилагаемый исходный код. - person yorkw; 02.05.2012
comment
Я попробую и дам вам знать... Большое спасибо за то, что вы потратили свое важное время на эту проблему.. Большое спасибо.. - person Kartik Domadiya; 02.05.2012
comment
Хорошая работа, сэр. +2. Как ты получил награду без галочки?! - person dokkaebi; 04.12.2012
comment
@dokkaebi Наконец-то я понял, как это сделать без взломанного VideoView. Смотрите мой ответ ниже. - person dcow; 10.12.2013
comment
@yorkw не могли бы вы помочь мне, пожалуйста, как мне справиться с проблемой, я также сталкиваюсь с той же проблемой, как только я поверну устройство, теперь оно начинает буферизацию - person Erum; 17.09.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();
    }   
}

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

person Khang .NT    schedule 13.08.2015
comment
Я получаю IllegalStateException здесь - person Lee Kang; 23.08.2018

Поскольку буфер теряется, когда просмотр видео переходит на задний план (изменение видимости), вы должны попытаться заблокировать это поведение, переопределив метод 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);
        }
    }
}
person Ronnie    schedule 26.04.2012
comment
Извините, я не могу понять вашу точку зрения? Не могли бы вы уточнить подробнее? - person Kartik Domadiya; 27.04.2012
comment
переопределение onWindowVisibilityChanged может заблокировать представление поверхности от разрушения поверхности при переходе к B.. VideoView является расширением SurfaceView... См. его код здесь.. grepcode.com/file/repository. grepcode.com/java/ext/ - person Ronnie; 27.04.2012
comment
Вы можете попробовать, имея статическую ссылку на VideoView.. не очень хорошая идея, но посмотрите, предотвращает ли это уничтожение видеопросмотра.. - person Ronnie; 27.04.2012
comment
Просто небольшой тест. Не приостанавливайте и не приостанавливайте. Пусть видео воспроизводится, пока вы переходите к следующему действию и возвращаетесь.. и также удалите возобновление калибровки.. Посмотрите, остается ли буфер.. Дайте нам знать, что произойдет.. - person Ronnie; 28.04.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) {
                }
}
person voidRy    schedule 01.05.2012
comment
Это то, что я сделал. Но он стирает буферизованную часть. - person Kartik Domadiya; 02.05.2012

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

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

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

Развлекайся! :)

person Szabolcs Berecz    schedule 27.04.2012

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

person MByD    schedule 28.04.2012

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

person dcow    schedule 10.12.2013
comment
Это звучит здорово. Я работал с API 14, поэтому мне нужно посмотреть, есть ли разумный способ заставить его работать так давно. Я испытаю это позже на неделе. - person dokkaebi; 10.12.2013

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

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

person Shankar Agarwal    schedule 25.04.2012
comment
videoView.pause() и videoViewresume() должны быть до super, а не после этого. - person Shankar Agarwal; 25.04.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();
}
person user936414    schedule 03.04.2012

person    schedule
comment
Спасибо за ответ. Это то, что я уже сделал. Но при возобновлении другого действия буферизованная часть видео теряется, и, следовательно, видео начинает буферизоваться с самого начала. - person Kartik Domadiya; 03.04.2012
comment
привет, вы можете попробовать использовать mediaPlay, в этом методе onPause/onResume хорошо используется - person Android; 03.04.2012