如何监控ExoPlayer
的进度变化?
我试图实现一个隐藏的MediaController
和重写setOnSeekBarChangeListener
方法,但现在没有成功。我想知道是否有另一种方式来听取ExoPlayer
的进展。
我知道这个问题很老了。但是,我在实施ExoPlayer
的同时落在了这一点上。这是为了帮助后来做同样事情的其他人:)
因此,我已按照以下方法跟踪播放进度。这是在ExoPlayer
Google Docs中完成的方式。它可以根据需要运行。
在PlayerControlView.java
结账Google ExoPlayer repository
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()
中调用updateProgressAction
,直到播放停止。只要状态发生变化,就会第一次调用该函数。我们使用removeCallbacks(Runnable runnable)
,因此总有一个updateProgressAction
值得关注。
@Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
updateProgressBar();
}
希望这可以帮助!
如果你想完成这个,就听听onPositionDiscontinuity()
。如果seekbar
正在擦洗,它会给你信息
试试吧,它为我工作:
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()
:轨道的持续时间,以毫秒为单位。不确定这是最好的方式,但我通过重载TrackRenderer
实现了这一点。
我正在使用videoPlayer.getBufferedPercentage()
,但你也可以自己计算百分比,只需使用TrackRenderer
的getBufferedPositionUs()
和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);
}
}
我使用RxJava找到了一个非常优雅的解决方案。这也涉及轮询模式,但我们确保每隔1秒使用一次间隔轮询。
public Observable<Long> playbackProgressObservable =
Observable.interval(1, TimeUnit.SECONDS)
这里的逻辑是我们创建一个Observable,它将每秒发出一个序号。然后我们使用map
运算符将数字转换为当前播放位置。
public Observable<Long> playbackProgressObservable =
Observable.interval(1, TimeUnit.SECONDS)
.map( { exoPlayer.getCurrentPosition() } );
为了最终将这些联系在一起,只需调用subscribe,ad每秒都会发出进度更新:
playbackProgressObservable.subscribe( { progress -> // Update logic here } )
注意:Observable.interval
运行在Scheduler
的默认Schedulers.computation()
上。因此,您可能需要添加observeOn()
运算符以确保将结果发送到正确的线程。
playbackProgressObservable
.observeOn(AndroidSchedulers.mainThead())
.subscribe(progress -> {}) // Update logic here
上述声明将为您提供一个Disposable,必须在您完成观察后处理。你可以这样做 - >
private var playbackDisposable: Disposable? = null
playbackDisposable = playbackProgressObservable
.observeOn(AndroidSchedulers.mainThead())
.subscribe(progress -> {}) // Update logic here
然后处置资源 - >
playbackDisposable?.dispose()
这至少适用于Exoplayer 2。
有四种播放状态:STATE_IDLE,STATE_BUFFERING,STATE_READY和STATE_ENDED。
检查播放状态很容易。至少有两个解决方案:if-statement或switch-statement。
无论播放状态如何,您都可以执行您的方法或设置其他内容,例如progressbar。
@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);
}
}
为了说清楚,在ProgressListener中没有为progress事件构建,但你可以在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的来源
我不确定这是否是正确的方法,但我使用EventBus和TimerTask
来更新正在播放的音频的进度。在我的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();
}
}
}
然后,当我将侦听器添加到SimpleExoPlayer实例时,我在onPlayerStateChanged
中调用了该方法。上面的代码通过EventBus每隔1秒(1000毫秒)发送正在播放的音频的经过时间和总持续时间。然后在托管SeekBar的活动中:
@Subscribe(threadMode = ThreadMode.MAIN)
public void updateProgress(ProgressNotification pn) {
seekBar.setMax((int) pn.duration);
seekBar.setProgress((int) pn.currentPosition);
}
扩展您当前的播放器类(例如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();
开始更新
仅将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;
}
});