Add chapter markers support to seekbar and update UI elements

This commit is contained in:
h
2026-10-09 13:09:41 +02:00
committed by Tobi
parent 0c20ddf8c7
commit 16c5de7ac9
3 changed files with 73 additions and 2 deletions
@@ -65,7 +65,9 @@ import org.schabi.newpipe.databinding.PlayerBinding;
import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.stream.AudioStream;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.StreamSegment;
import org.schabi.newpipe.extractor.stream.VideoStream;
import org.schabi.newpipe.views.ChaptersSeekBar;
import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
import org.schabi.newpipe.ktx.AnimationType;
import org.schabi.newpipe.player.Player;
@@ -85,6 +87,7 @@ import org.schabi.newpipe.util.external_communication.KoreUtils;
import org.schabi.newpipe.util.external_communication.ShareUtils;
import org.schabi.newpipe.views.player.PlayerFastSeekOverlay;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
@@ -147,6 +150,9 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa
private final SeekbarPreviewThumbnailHolder seekbarPreviewThumbnailHolder =
new SeekbarPreviewThumbnailHolder();
@NonNull
private List<StreamSegment> currentChapters = Collections.emptyList();
/*//////////////////////////////////////////////////////////////////////////
// Constructor, setup, destroy
@@ -589,6 +595,14 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa
binding.currentSeekbarPreviewThumbnail,
binding.subtitleView::getWidth);
// Chapter title tooltip
if (!currentChapters.isEmpty()) {
final StreamSegment chapter = getChapterAtMs(progress);
if (chapter != null && chapter.getTitle() != null) {
binding.currentChapterTitle.setText(chapter.getTitle());
}
}
adjustSeekbarPreviewContainer();
}
@@ -642,6 +656,10 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa
AnimationType.SCALE_AND_ALPHA);
animate(binding.currentSeekbarPreviewThumbnail, true, DEFAULT_CONTROLS_DURATION,
AnimationType.SCALE_AND_ALPHA);
if (!currentChapters.isEmpty()) {
animate(binding.currentChapterTitle, true, DEFAULT_CONTROLS_DURATION,
AnimationType.SCALE_AND_ALPHA);
}
}
@Override // seekbar listener
@@ -658,6 +676,7 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa
binding.playbackCurrentTime.setText(getTimeString(seekBar.getProgress()));
animate(binding.currentDisplaySeek, false, 200, AnimationType.SCALE_AND_ALPHA);
animate(binding.currentSeekbarPreviewThumbnail, false, 200, AnimationType.SCALE_AND_ALPHA);
animate(binding.currentChapterTitle, false, 200, AnimationType.SCALE_AND_ALPHA);
if (player.getCurrentState() == STATE_PAUSED_SEEK) {
player.changeState(STATE_BUFFERING);
@@ -668,6 +687,25 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa
showControlsThenHide();
}
/**
* Returns the chapter active at the given playback position, or {@code null} if
* {@code currentChapters} is empty.
*
* @param positionMs playback position in milliseconds
* @return the {@link StreamSegment} whose window contains {@code positionMs}
*/
@Nullable
private StreamSegment getChapterAtMs(final long positionMs) {
StreamSegment result = null;
for (final StreamSegment seg : currentChapters) {
if (seg.getStartTimeSeconds() * 1000L > positionMs) {
break;
}
result = seg;
}
return result;
}
//endregion
@@ -1036,6 +1074,22 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa
binding.channelTextView.setText(info.getUploaderName());
this.seekbarPreviewThumbnailHolder.resetFrom(player.getContext(), info.getPreviewFrames());
// Chapter markers on seekbar
currentChapters = info.getStreamSegments() != null
? info.getStreamSegments() : Collections.emptyList();
Log.d(TAG, "onMetadataChanged: seekBarClass="
+ binding.playbackSeekBar.getClass().getSimpleName()
+ " segments=" + currentChapters.size()
+ " duration=" + info.getDuration());
if (binding.playbackSeekBar instanceof ChaptersSeekBar) {
((ChaptersSeekBar) binding.playbackSeekBar)
.setChapters(currentChapters, info.getDuration());
} else {
Log.e(TAG, "onMetadataChanged: playbackSeekBar is NOT a ChaptersSeekBar! "
+ "Check that player.xml was rebuilt.");
}
binding.currentChapterTitle.setVisibility(View.GONE);
}
private void updateStreamRelatedViews() {
@@ -33,7 +33,7 @@ import org.schabi.newpipe.util.DeviceUtils;
* (onStartTrackingTouch/onStopTrackingTouch), so existing code does not need to be changed to
* work with it.
*/
public final class FocusAwareSeekBar extends AppCompatSeekBar {
public class FocusAwareSeekBar extends AppCompatSeekBar {
private NestedListener listener;
private ViewTreeObserver treeObserver;
+18 -1
View File
@@ -415,6 +415,23 @@
android:orientation="vertical"
android:paddingBottom="12dp">
<org.schabi.newpipe.views.NewPipeTextView
android:id="@+id/currentChapterTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#60000000"
android:ellipsize="end"
android:maxLines="1"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:paddingBottom="2dp"
android:textColor="@android:color/white"
android:textSize="12sp"
android:visibility="gone"
tools:ignore="RtlHardcoded"
tools:text="Introduction"
tools:visibility="visible" />
<org.schabi.newpipe.views.NewPipeTextView
android:id="@+id/currentDisplaySeek"
android:layout_width="wrap_content"
@@ -467,7 +484,7 @@
tools:text="1:06:29" />
<org.schabi.newpipe.views.FocusAwareSeekBar
<org.schabi.newpipe.views.ChaptersSeekBar
android:id="@+id/playbackSeekBar"
style="@style/Widget.AppCompat.SeekBar"
android:layout_width="0dp"