6565import org .schabi .newpipe .extractor .MediaFormat ;
6666import org .schabi .newpipe .extractor .stream .AudioStream ;
6767import org .schabi .newpipe .extractor .stream .StreamInfo ;
68+ import org .schabi .newpipe .extractor .stream .StreamSegment ;
6869import org .schabi .newpipe .extractor .stream .VideoStream ;
70+ import org .schabi .newpipe .views .ChaptersSeekBar ;
6971import org .schabi .newpipe .fragments .detail .VideoDetailFragment ;
7072import org .schabi .newpipe .ktx .AnimationType ;
7173import org .schabi .newpipe .player .Player ;
8587import org .schabi .newpipe .util .external_communication .ShareUtils ;
8688import org .schabi .newpipe .views .player .PlayerFastSeekOverlay ;
8789
90+ import java .util .Collections ;
8891import java .util .List ;
8992import java .util .Objects ;
9093import java .util .Optional ;
@@ -147,6 +150,9 @@ private enum PlayButtonAction {
147150 private final SeekbarPreviewThumbnailHolder seekbarPreviewThumbnailHolder =
148151 new SeekbarPreviewThumbnailHolder ();
149152
153+ @ NonNull
154+ private List <StreamSegment > currentChapters = Collections .emptyList ();
155+
150156
151157 /*//////////////////////////////////////////////////////////////////////////
152158 // Constructor, setup, destroy
@@ -585,6 +591,14 @@ public void onProgressChanged(final SeekBar seekBar, final int progress,
585591 binding .currentSeekbarPreviewThumbnail ,
586592 binding .subtitleView ::getWidth );
587593
594+ // Chapter title tooltip
595+ if (!currentChapters .isEmpty ()) {
596+ final StreamSegment chapter = getChapterAtMs (progress );
597+ if (chapter != null && chapter .getTitle () != null ) {
598+ binding .currentChapterTitle .setText (chapter .getTitle ());
599+ }
600+ }
601+
588602 adjustSeekbarPreviewContainer ();
589603 }
590604
@@ -638,6 +652,10 @@ public void onStartTrackingTouch(final SeekBar seekBar) {
638652 AnimationType .SCALE_AND_ALPHA );
639653 animate (binding .currentSeekbarPreviewThumbnail , true , DEFAULT_CONTROLS_DURATION ,
640654 AnimationType .SCALE_AND_ALPHA );
655+ if (!currentChapters .isEmpty ()) {
656+ animate (binding .currentChapterTitle , true , DEFAULT_CONTROLS_DURATION ,
657+ AnimationType .SCALE_AND_ALPHA );
658+ }
641659 }
642660
643661 @ Override // seekbar listener
@@ -654,6 +672,7 @@ public void onStopTrackingTouch(final SeekBar seekBar) {
654672 binding .playbackCurrentTime .setText (getTimeString (seekBar .getProgress ()));
655673 animate (binding .currentDisplaySeek , false , 200 , AnimationType .SCALE_AND_ALPHA );
656674 animate (binding .currentSeekbarPreviewThumbnail , false , 200 , AnimationType .SCALE_AND_ALPHA );
675+ animate (binding .currentChapterTitle , false , 200 , AnimationType .SCALE_AND_ALPHA );
657676
658677 if (player .getCurrentState () == STATE_PAUSED_SEEK ) {
659678 player .changeState (STATE_BUFFERING );
@@ -664,6 +683,25 @@ public void onStopTrackingTouch(final SeekBar seekBar) {
664683
665684 showControlsThenHide ();
666685 }
686+
687+ /**
688+ * Returns the chapter active at the given playback position, or {@code null} if
689+ * {@code currentChapters} is empty.
690+ *
691+ * @param positionMs playback position in milliseconds
692+ * @return the {@link StreamSegment} whose window contains {@code positionMs}
693+ */
694+ @ Nullable
695+ private StreamSegment getChapterAtMs (final long positionMs ) {
696+ StreamSegment result = null ;
697+ for (final StreamSegment seg : currentChapters ) {
698+ if (seg .getStartTimeSeconds () * 1000L > positionMs ) {
699+ break ;
700+ }
701+ result = seg ;
702+ }
703+ return result ;
704+ }
667705 //endregion
668706
669707
@@ -1020,6 +1058,22 @@ public void onMetadataChanged(@NonNull final StreamInfo info) {
10201058 binding .channelTextView .setText (info .getUploaderName ());
10211059
10221060 this .seekbarPreviewThumbnailHolder .resetFrom (player .getContext (), info .getPreviewFrames ());
1061+
1062+ // Chapter markers on seekbar
1063+ currentChapters = info .getStreamSegments () != null
1064+ ? info .getStreamSegments () : Collections .emptyList ();
1065+ Log .d (TAG , "onMetadataChanged: seekBarClass="
1066+ + binding .playbackSeekBar .getClass ().getSimpleName ()
1067+ + " segments=" + currentChapters .size ()
1068+ + " duration=" + info .getDuration ());
1069+ if (binding .playbackSeekBar instanceof ChaptersSeekBar ) {
1070+ ((ChaptersSeekBar ) binding .playbackSeekBar )
1071+ .setChapters (currentChapters , info .getDuration ());
1072+ } else {
1073+ Log .e (TAG , "onMetadataChanged: playbackSeekBar is NOT a ChaptersSeekBar! "
1074+ + "Check that player.xml was rebuilt." );
1075+ }
1076+ binding .currentChapterTitle .setVisibility (View .GONE );
10231077 }
10241078
10251079 private void updateStreamRelatedViews () {
0 commit comments