45

如何监控进度变化ExoPlayer
我试图实现一个隐藏MediaController和覆盖的setOnSeekBarChangeListener方法,但现在没有成功。我想知道是否有另一种方式来听取ExoPlayer进展情况。

4

15 回答 15

36

我知道这个问题很老了。但是,我在实施ExoPlayer. 这是为了帮助以后做同样事情的其他人:)

因此,我按照以下方法跟踪播放进度。这是在ExoPlayerGoogle Docs 中完成的方式。它根据需要工作。

PlayerControlView.javaGoogle ExoPlayer 存储库中结帐

updateProgressBar()是更新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();
    }
};

我们反复调用updateProgressBar()insideupdateProgressAction直到播放停止。只要状态发生变化,就会第一次调用该函数。我们使用removeCallbacks(Runnable runnable),以便总有一个updateProgressAction需要关心。

@Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
  updateProgressBar();
}

希望这可以帮助!

于 2016-09-30T12:14:33.587 回答
20

试试这个,它对我有用:

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

这里,

  • getCurrentPosition(): 返回当前播放位置,以毫秒为单位。
  • getDuration():曲目的持续时间(以毫秒为单位)。
于 2017-06-13T08:35:51.050 回答
14

我找到了一个使用 RxJava 的非常优雅的解决方案。这也涉及轮询模式,但我们确保使用间隔每 1 秒轮询一次。

public Observable<Long> playbackProgressObservable = 
                            Observable.interval(1, TimeUnit.SECONDS, AndroidSchedulers.mainThread())

这里的逻辑是我们创建一个 Observable,它将每秒发出一个序列号。然后我们使用map运算符将​​数字转换为当前播放位置。

public Observable<Long> playbackProgressObservable = 
                        Observable.interval(1, TimeUnit.SECONDS)
                                  .map( { exoPlayer.getCurrentPosition() } );

要最终将其连接在一起,只需调用 subscribe,就会每秒发出一次进度更新:

playbackProgressObservable.subscribe( { progress -> // Update logic here } )

注意:Observable.interval在默认情况下Scheduler运行Schedulers.computation(). 因此,您可能需要添加一个observeOn()运算符以确保将结果发送到正确的线程。

playbackProgressObservable
    .observeOn(AndroidSchedulers.mainThread())        
    .subscribe(progress -> {}) // Update logic here 

上面的语句会给你一个Disposable,当你完成观察时必须将其丢弃。你可以做这样的事情->

private var playbackDisposable: Disposable? = null

       playbackDisposable = playbackProgressObservable
        .observeOn(AndroidSchedulers.mainThead())        
        .subscribe(progress -> {}) // Update logic here

然后处置资源->

playbackDisposable?.dispose()
于 2018-05-07T20:16:07.920 回答
3

不确定这是最好的方法,但我通过重载TrackRenderer.

我正在使用videoPlayer.getBufferedPercentage(),但您也可以自己计算百分比,只需使用TrackRenderer'sgetBufferedPositionUs()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);
    }

}
于 2015-06-02T07:00:46.283 回答
2

我不确定这是否是正确的方法,但我使用了 EventBusTimerTask更新了正在播放的音频的进度。在我的 MusicController 类中,我放了:

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

}

onPlayerStateChanged然后我在将侦听器添加到我的 SimpleExoPlayer 实例时调用了里面的方法。上面的代码通过 EventBus 每 1 秒(1000 毫秒)发送正在播放的音频的经过时间和总持续时间。然后在托管 SeekBar 的活动中:

@Subscribe(threadMode = ThreadMode.MAIN)
    public void updateProgress(ProgressNotification pn) {
        seekBar.setMax((int) pn.duration);
        seekBar.setProgress((int) pn.currentPosition);
    }
于 2018-06-10T11:27:54.147 回答
2

为了清楚起见,在 EventListener 中没有为进度事件构建,但是您可以在 updateProgress() 函数中调用 Handler.postDelayed 来获取当前进度

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

更多详细信息,请参见Exoplayer中PlaybackControlView.java的源代码

于 2017-05-09T15:53:00.620 回答
1

好吧,我是通过 kotlin flow 做到的。

private fun audioProgress(exoPlayer: SimpleExoPlayer?) = flow<Int> {
        while (true) {
            emit(((exoPlayer?.currentPosition?.toFloat()?.div(exoPlayer.duration.toFloat())?.times(100))?.toInt()!!))
            delay(1000)
        }
    }.flowOn(Dispatchers.IO)

然后像这样收集进度...

val audioProgressJob = launch {
            audioProgress(exoPlayer).collect {
                MP_progress_bar.progress = it
            }
        }
于 2021-01-06T15:49:24.003 回答
1

我也有这个问题,我在这个链接上找到了解决方案

解决方案:

1.创建一个这样的类:

   public class ProgressTracker implements Runnable {

        public interface PositionListener{
            public void progress(long position);
        }
    
        private final Player player;
        private final Handler handler;
        private PositionListener positionListener;
    
        public ProgressTracker(Player player, PositionListener positionListener) {
            this.player = player;
            this.positionListener = positionListener;
            handler = new Handler();
            handler.post(this);
        }
    
        public void run() {
            long position = player.getCurrentPosition();
            positionListener.progress(position);
            handler.postDelayed(this, 1000);
        }
    
        public void purgeHandler() {
            handler.removeCallbacks(this);
        }
    }

2.最后在你的代码中使用它:

tracker = new ProgressTracker(player, new ProgressTracker.PositionListener() {
        @Override
        public void progress(long position) {
            Log.i(TAG, "VideoViewActivity/progress: position=" + position);
        }
    });

3.在最后一步,当你想要释放播放器时不要忘记调用 purgeHandler (重要)

tracker.purgeHandler();
player.release();
player = null;
于 2020-12-07T13:09:56.920 回答
0

扩展您当前的播放器类(例如 SimpleExoPlayer)并添加

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

然后在播放器活动中只需实现接口并开始更新player.startProgressUpdater();

于 2017-02-22T11:11:34.940 回答
0

rx java实现:

private val disposablesVideoControlsDisposable = CompositeDisposable()

fun showVideoControlsAndSimilarTray() {
    videoSeekBar?.setOnSeekBarChangeListener(object :
        SeekBar.OnSeekBarChangeListener {
        override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
            if (fromUser) seekVideoProgress(progress)
        }

        override fun onStartTrackingTouch(seekBar: SeekBar?) {}
        override fun onStopTrackingTouch(seekBar: SeekBar?) {}

    })
    val disposable = Observable.interval(0, 1, TimeUnit.SECONDS)
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe {
            calculateVideoProgress()
        }
    disposablesVideoControlsDisposable.add(disposable)
}

private fun calculateVideoProgress() {
    val currentMill = exoPlayer.currentPosition
    val totalMillis = exoPlayer.duration
    if (totalMillis > 0L) {
        val remainMillis = (totalMillis - currentMill).toFloat() / 1000
        val remainMins = (remainMillis / 60).toInt()
        val remainSecs = (remainMillis % 60).toInt()
        videoProgressText.setText("$remainMins:${String.format("%02d", remainSecs)}")
        seekBarProgress.set((currentMill.toFloat() / totalMillis * 100).toInt())
    }
    
}

private fun seekVideoProgress(progress: Int) {
    val seekMillis = exoPlayer.duration.toFloat() * progress / 100
    exoPlayer.seekTo(seekMillis.toLong())
}

最后当你完成时:

   fun disposeVideoControlsObservable() {
    disposablesVideoControlsDisposable.clear()
}
于 2021-06-14T16:05:17.417 回答
0

如果你想做到这一点,只要听onPositionDiscontinuity()seekbar如果正在擦洗,它将为您提供信息

于 2019-03-06T07:42:22.017 回答
0

仅将 onTouchListener 与 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;
                }
            });
于 2018-01-22T20:57:31.560 回答
0

如果您完全或部分使用播放器视图或播放器控制视图(仅用于按钮或其他内容),您可以直接从中设置进度侦听器:

PlayerControlView playerControlView = miniPlayerCardView.findViewById(R.id.playerView);
ProgressBar audioProgressBar = miniPlayerCardView.findViewById(R.id.audioProgressBar);

playerControlView.setProgressUpdateListener((position, bufferedPosition) -> {
            int progressBarPosition = (int) ((position*100)/player.getDuration());
            int bufferedProgressBarPosition = (int) ((bufferedPosition*100)/player.getDuration());
            audioProgressBar.setProgress(progressBarPosition);
            audioProgressBar.setSecondaryProgress(bufferedProgressBarPosition);
        });
于 2020-06-06T07:46:29.260 回答
-1

这很简单

var player = SimpleExoPlayer.Builder(context).build();
player.addListener(object:Player.Listener{
     override fun onEvents(player: Player, events: Player.Events) {
        super.onEvents(player, events)
                if (events.containsAny(
                Player.EVENT_IS_LOADING_CHANGED,
                Player.EVENT_PLAYBACK_STATE_CHANGED,
                Player.EVENT_PLAY_WHEN_READY_CHANGED,
                Player.EVENT_IS_PLAYING_CHANGED
              )) {
                   log(msg="progres ${player.currentPosition}") 
               }
     }

})

您可以在 1356 行查看 com.google.android.exoplayer2.ui.PlayerControlView.java

于 2021-06-15T04:30:41.113 回答
-1

这至少适用于 Exoplayer 2。

有四种播放状态:STATE_IDLE、STATE_BUFFERING、STATE_READY 和 STATE_ENDED。

检查播放状态很容易。至少有两种解决方案:if 语句或 switch 语句。

无论播放状态如何,您都可以执行您的方法或设置其他内容,例如进度条。

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

}
于 2017-01-23T18:29:49.703 回答