Android ExoPlayer en ProgressChanged
listener (9)
¿Cómo puedo monitorear los cambios de progreso en ExoPlayer
?
Intenté implementar un MediaController
oculto y anular los métodos setOnSeekBarChangeListener
, pero por ahora sin éxito. Me pregunto si hay otra manera de escuchar el progreso de ExoPlayer
.
Amplíe su clase de jugador actual (SimpleExoPlayer por ej.) Y agregue
public interface PlayerEventsListener {
void onSeek(int from, int to);
void onProgressUpdate(long progress);
}
private PlayerEventsListener mListener;
private Handler mHandler;
private Runnable mProgressUpdater;
private boolean isUpdatingProgress = false;
public SomePlayersConstructor(Activity activity, /*...*/) {
//...
mListener = (PlayerEventsListener) activity;
mHandler = new Handler();
mProgressUpdater = new ProgressUpdater();
}
// Here u gain access to seek events
@Override
public void seekTo(long positionMs) {
mListener.onSeek(-1, (int)positionMs/1000);
super.seekTo(positionMs);
}
@Override
public void seekTo(int windowIndex, long positionMs) {
mListener.onSeek((int)getCurrentPosition()/1000, (int)positionMs/1000);
super.seekTo(windowIndex, positionMs);
}
// Here u gain access to progress
public void startProgressUpdater() {
if (!isUpdatingProgress) {
mProgressUpdater.run();
isUpdatingProgress = true;
}
}
private class ProgressUpdater implements Runnable {
private static final int TIME_UPDATE_MS = 500;
@Override
public void run() {
mListener.onProgressUpdate(getCurrentPosition());
mHandler.postDelayed(mProgressUpdater, TIME_UPDATE_MS);
}
}
Luego, dentro de la actividad del jugador, simplemente implementa la interfaz y comienza las actualizaciones con player.startProgressUpdater();
Esto funciona al menos con Exoplayer 2.
Hay cuatro estados de reproducción: STATE_IDLE, STATE_BUFFERING, STATE_READY y STATE_ENDED.
Verificar el estado de reproducción es fácil de hacer. Hay al menos dos soluciones: if-statement o switch-statement.
Cualquiera que sea el estado de reproducción en curso, puede ejecutar su método o configurar otra cosa, por ejemplo, barra de progreso.
@Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
if (playbackState == ExoPlayer.STATE_ENDED) {
showControls();
Toast.makeText(getApplicationContext(), "Playback ended", Toast.LENGTH_LONG).show();
}
else if (playbackState == ExoPlayer.STATE_BUFFERING)
{
progressBar.setVisibility(View.VISIBLE);
Toast.makeText(getApplicationContext(), "Buffering..", Toast.LENGTH_SHORT).show();
}
else if (playbackState == ExoPlayer.STATE_READY)
{
progressBar.setVisibility(View.INVISIBLE);
}
}
He encontrado una solución bastante elegante usando RxJava. Esto también implica un patrón de sondeo, pero nos aseguramos de usar un intervalo para sondear cada 1 segundo.
public Observable<Long> playbackProgressObservable =
Observable.interval(1, TimeUnit.SECONDS)
La lógica aquí es que creamos un Observable que emitirá un número secuencial cada segundo. Luego usamos el operador del map
para transformar el número en la posición de reproducción actual.
public Observable<Long> playbackProgressObservable =
Observable.interval(1, TimeUnit.SECONDS)
.map( { exoPlayer.getCurrentPosition() } );
Para finalmente conectar esto, simplemente llame a suscribirse, y los cambios de progreso se emitirán cada segundo:
playbackProgressObservable.subscribe( { progress -> // Update logic here } )
Nota: Observable.interval
ejecuta en un Scheduler
predeterminado de Schedulers.computation()
. Por lo tanto, es probable que deba agregar un operador observeOn()
para asegurarse de que los resultados se envíen al hilo correcto.
playbackProgressObservable
.observeOn(AndroidSchedulers.mainThead())
.subscribe(progress -> {}) // Update logic here
La declaración anterior le proporcionará un Desechable que debe eliminarse cuando haya terminado de observar. Puedes hacer algo como esto ->
private var playbackDisposable: Disposable? = null
playbackDisposable = playbackProgressObservable
.observeOn(AndroidSchedulers.mainThead())
.subscribe(progress -> {}) // Update logic here
luego para disponer el recurso ->
playbackDisposable?.dispose()
No estoy seguro de que sea la mejor manera, pero logré esto al sobrecargar el TrackRenderer
.
Estoy usando videoPlayer.getBufferedPercentage()
, pero es posible que usted también pueda calcular el porcentaje mediante el uso de getBufferedPositionUs()
y getDurationUs()
public interface ProgressListener {
public void onProgressChange(long progress);
}
public class CustomVideoRenderer extends MediaCodecVideoTrackRenderer {
long progress = 0;
private final CopyOnWriteArraySet<ProgressListener> progressListeners = new CopyOnWriteArraySet();
// [...]
// Skipped constructors
// [...]
public void doSomeWork(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
super.doSomeWork(positionUs, elapsedRealtimeUs);
long tmpProgress = videoPlayer.getBufferedPercentage();
if (tmpProgress != this.progress) {
this.progress = tmpProgress;
for (ProgressListener progressListener : this.progressListeners) {
progressListener.onProgressChange(progress);
}
}
}
public void addProgressListener(ProgressListener listener) {
this.progressListeners.add(listener);
}
}
No estoy seguro de si es el enfoque correcto, pero utilicé EventBus y TimerTask
para actualizar el progreso del audio que se está reproduciendo. En mi clase de MusicController pongo:
private void sendElapsedDuration() {
//To send the current elapsed time
final Timer t = new Timer();
Runnable r = new Runnable() {
@Override
public void run() {
t.schedule(new TimerTask() {
@Override
public void run() {
EventBus.getDefault().post(
new ProgressNotification(
player.getCurrentPosition(), player.getDuration())
);
if (player.getCurrentPosition() >= player.getDuration() ){
// The audio is ended, we pause the playback,
// and reset the position
player.seekTo(0);
player.setPlayWhenReady(false);
this.cancel();
// stopping the Runnable to avoid memory leak
mainHandler.removeCallbacks(this);
}
}
},0,1000);
}
};
if(player != null) {
if (player.getPlaybackState() != Player.STATE_ENDED)
mainHandler.postDelayed(r, 500);
else {
//We put the TimerTask to sleep when audio is not playing
t.cancel();
}
}
}
Luego llamé al método dentro de onPlayerStateChanged
al agregar el escucha a mi instancia de SimpleExoPlayer. El código anterior envía la duración transcurrida y total del audio que se reproduce cada 1 segundo (1000 ms) a través de EventBus. Luego dentro de la actividad que alberga el SeekBar:
@Subscribe(threadMode = ThreadMode.MAIN)
public void updateProgress(ProgressNotification pn) {
seekBar.setMax((int) pn.duration);
seekBar.setProgress((int) pn.currentPosition);
}
Para que quede claro, no hay una compilación en EventListener para el evento progress, pero puede llamar a Handler.postDelayed dentro de su función updateProgress () para obtener el progreso actual
private void updateProgress(){
//get current progress
long position = player == null ? 0 : player.getCurrentPosition();
//updateProgress() will be called repeatedly, you can check
//player state to end it
handler.postDelayed(updateProgressAction,1000)
}
private final Runnable updateProgressAction = new Runnable() {
@Override
public void run() {
updateProgress();
}
};
Para más detalles, consulte la fuente de PlaybackControlView.java dentro de Exoplayer
Sé que esta pregunta es muy antigua. Pero, aterricé en esto mientras implementaba ExoPlayer
. Esto es para ayudar a los otros que hacen lo mismo más adelante :)
Por lo tanto, he seguido los siguientes métodos para realizar un seguimiento del progreso de la reproducción. Así es como se hace en Google Docs de ExoPlayer
. Funciona como sea necesario.
Checkout PlayerControlView.java
en el repositorio de Google ExoPlayer
updateProgressBar()
es la función para actualizar el progreso de SeekBar
:
private void updateProgressBar() {
long duration = player == null ? 0 : player.getDuration();
long position = player == null ? 0 : player.getCurrentPosition();
if (!dragging) {
mSeekBar.setProgress(progressBarValue(position));
}
long bufferedPosition = player == null ? 0 : player.getBufferedPosition();
mSeekBar.setSecondaryProgress(progressBarValue(bufferedPosition));
// Remove scheduled updates.
handler.removeCallbacks(updateProgressAction);
// Schedule an update if necessary.
int playbackState = player == null ? Player.STATE_IDLE : player.getPlaybackState();
if (playbackState != Player.STATE_IDLE && playbackState != Player.STATE_ENDED) {
long delayMs;
if (player.getPlayWhenReady() && playbackState == Player.STATE_READY) {
delayMs = 1000 - (position % 1000);
if (delayMs < 200) {
delayMs += 1000;
}
} else {
delayMs = 1000;
}
handler.postDelayed(updateProgressAction, delayMs);
}
}
private final Runnable updateProgressAction = new Runnable() {
@Override
public void run() {
updateProgressBar();
}
};
Llamamos a updateProgressBar()
dentro de updateProgressAction
repetidamente hasta que la reproducción se detiene. La función se llama la primera vez siempre que hay un cambio de estado. Usamos removeCallbacks(Runnable runnable)
para que siempre haya un updateProgressAction
para cuidar.
@Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
updateProgressBar();
}
¡Espero que esto ayude!
Solo prueba esto, está funcionando para mí:
handler = new Handler();
runnable = new Runnable() {
@Override
public void run() {
progressbar.setProgress((int) ((exoPlayer.getCurrentPosition()*100)/exoPlayer.getDuration()));
handler.postDelayed(runnable, 1000);
}
};
handler.postDelayed(runnable, 0);
Aquí,
-
getCurrentPosition()
: return La posición de reproducción actual en milisegundos. -
getDuration()
: la duración de la pista en milisegundos.
Solo use onTouchListener con MotionEvent.ACTION_UP
SeekBar exo_progress = (SeekBar) findViewById(R.id.exo_progress);
exo_progress.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
//put your code here!!
}
return false;
}
});