6666import org .schabi .newpipe .extractor .MediaFormat ;
6767import org .schabi .newpipe .extractor .stream .AudioStream ;
6868import org .schabi .newpipe .extractor .stream .StreamInfo ;
69+ import org .schabi .newpipe .extractor .stream .StreamSegment ;
6970import org .schabi .newpipe .extractor .stream .VideoStream ;
71+ import org .schabi .newpipe .views .ChaptersSeekBar ;
7072import org .schabi .newpipe .fragments .detail .VideoDetailFragment ;
7173import org .schabi .newpipe .ktx .AnimationType ;
7274import org .schabi .newpipe .player .Player ;
8688import org .schabi .newpipe .util .external_communication .ShareUtils ;
8789import org .schabi .newpipe .views .player .PlayerFastSeekOverlay ;
8890
91+ import java .util .Collections ;
8992import java .util .List ;
9093import java .util .Objects ;
9194import java .util .Optional ;
@@ -148,6 +151,9 @@ private enum PlayButtonAction {
148151 private final SeekbarPreviewThumbnailHolder seekbarPreviewThumbnailHolder =
149152 new SeekbarPreviewThumbnailHolder ();
150153
154+ @ NonNull
155+ private List <StreamSegment > currentChapters = Collections .emptyList ();
156+
151157
152158 /*//////////////////////////////////////////////////////////////////////////
153159 // Constructor, setup, destroy
@@ -586,6 +592,14 @@ public void onProgressChanged(final SeekBar seekBar, final int progress,
586592 binding .currentSeekbarPreviewThumbnail ,
587593 binding .subtitleView ::getWidth );
588594
595+ // Chapter title tooltip
596+ if (!currentChapters .isEmpty ()) {
597+ final StreamSegment chapter = getChapterAtMs (progress );
598+ if (chapter != null && chapter .getTitle () != null ) {
599+ binding .currentChapterTitle .setText (chapter .getTitle ());
600+ }
601+ }
602+
589603 adjustSeekbarPreviewContainer ();
590604 }
591605
@@ -639,6 +653,10 @@ public void onStartTrackingTouch(final SeekBar seekBar) {
639653 AnimationType .SCALE_AND_ALPHA );
640654 animate (binding .currentSeekbarPreviewThumbnail , true , DEFAULT_CONTROLS_DURATION ,
641655 AnimationType .SCALE_AND_ALPHA );
656+ if (!currentChapters .isEmpty ()) {
657+ animate (binding .currentChapterTitle , true , DEFAULT_CONTROLS_DURATION ,
658+ AnimationType .SCALE_AND_ALPHA );
659+ }
642660 }
643661
644662 @ Override // seekbar listener
@@ -655,6 +673,7 @@ public void onStopTrackingTouch(final SeekBar seekBar) {
655673 binding .playbackCurrentTime .setText (getTimeString (seekBar .getProgress ()));
656674 animate (binding .currentDisplaySeek , false , 200 , AnimationType .SCALE_AND_ALPHA );
657675 animate (binding .currentSeekbarPreviewThumbnail , false , 200 , AnimationType .SCALE_AND_ALPHA );
676+ animate (binding .currentChapterTitle , false , 200 , AnimationType .SCALE_AND_ALPHA );
658677
659678 if (player .getCurrentState () == STATE_PAUSED_SEEK ) {
660679 player .changeState (STATE_BUFFERING );
@@ -665,6 +684,25 @@ public void onStopTrackingTouch(final SeekBar seekBar) {
665684
666685 showControlsThenHide ();
667686 }
687+
688+ /**
689+ * Returns the chapter active at the given playback position, or {@code null} if
690+ * {@code currentChapters} is empty.
691+ *
692+ * @param positionMs playback position in milliseconds
693+ * @return the {@link StreamSegment} whose window contains {@code positionMs}
694+ */
695+ @ Nullable
696+ private StreamSegment getChapterAtMs (final long positionMs ) {
697+ StreamSegment result = null ;
698+ for (final StreamSegment seg : currentChapters ) {
699+ if (seg .getStartTimeSeconds () * 1000L > positionMs ) {
700+ break ;
701+ }
702+ result = seg ;
703+ }
704+ return result ;
705+ }
668706 //endregion
669707
670708
@@ -1023,6 +1061,22 @@ public void onMetadataChanged(@NonNull final StreamInfo info) {
10231061 binding .channelTextView .setText (info .getUploaderName ());
10241062
10251063 this .seekbarPreviewThumbnailHolder .resetFrom (player .getContext (), info .getPreviewFrames ());
1064+
1065+ // Chapter markers on seekbar
1066+ currentChapters = info .getStreamSegments () != null
1067+ ? info .getStreamSegments () : Collections .emptyList ();
1068+ Log .d (TAG , "onMetadataChanged: seekBarClass="
1069+ + binding .playbackSeekBar .getClass ().getSimpleName ()
1070+ + " segments=" + currentChapters .size ()
1071+ + " duration=" + info .getDuration ());
1072+ if (binding .playbackSeekBar instanceof ChaptersSeekBar ) {
1073+ ((ChaptersSeekBar ) binding .playbackSeekBar )
1074+ .setChapters (currentChapters , info .getDuration ());
1075+ } else {
1076+ Log .e (TAG , "onMetadataChanged: playbackSeekBar is NOT a ChaptersSeekBar! "
1077+ + "Check that player.xml was rebuilt." );
1078+ }
1079+ binding .currentChapterTitle .setVisibility (View .GONE );
10261080 }
10271081
10281082 private void updateStreamRelatedViews () {
0 commit comments