I want to implement a scrubber for ExoPlayer, to show a thumbnail preview of the position the user is seeking to. While my current implementation is working, the thumbnail only refreshes after the user has stopped dragging the seekbar view, or paused for an instant. It appears as if the seek command on ExoPlayer is implementing something like a debounce function, to prevent too many seeks in succession.
While I can work around this by doing my own throttling on the seekbar callback and send a seek request every x milliseconds, I would like to know if there is a way to tell ExoPlayer to not drop seek requests so the thumbnail can update in real time.
For reference, this is my current implementation:
Subject for throttling:
private final Subject<Long> subject = PublishSubject.create();
Observer for seek requests with throttling:
subject
.throttleLast(400L, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(position -> exoPlayer.seekTo(position));
Seekbar callback:
@Override
public void onProgressChanged(@NonNull final SeekBar seekBar,
final int progress,
final boolean fromUser) {
if (fromUser) {
final long position = computePosition(progress);
subject.onNext(position);
}
}
This is old, but might be useful for someone:
As of 2.7.0 ExoPlayer has the method Player.setSeekParameters()
If you want to snap the seek position to the closest keyframe use SeekParameters.CLOSEST_SYNC
(faster but less accurate). To see to a exact frame (the frame before the requested position, even if it's not a keyframe) use SeekParameters.EXACT
.
I don't know if this is the optimal solution, but you can look at this open source project which achieves roughly something you're looking for. Here is the Github.
It looks like they use a custom LoadControl to get ExoPlayer to render frames quicker. And I believe they're compounding that with selection of the worst quality so it loads even faster.
This is the code they use in that library's onProgressChange(...)
:
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if(fromUser){
int offset = (float) progress / seekBar.getMax()
// Round the offset before seeking. The sample uses 1% or 10% of the video per each thumbnail
previewPlayer.seekTo((long) (offset * previewPlayer.getDuration()));
previewPlayer.setPlayWhenReady(false);
}
}
Another way to do this is to actually grab a thumbnail off of the TextureView
- which they cover in this issue. The caveat being there are some limitations to using multiple renderers.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With