Chapter dividers for the progress bar (#4915)
This commit is contained in:
parent
d444e8d23e
commit
a476ce2f47
|
@ -29,6 +29,7 @@ import de.danoeh.antennapod.activity.CastEnabledActivity;
|
||||||
import de.danoeh.antennapod.activity.MainActivity;
|
import de.danoeh.antennapod.activity.MainActivity;
|
||||||
import de.danoeh.antennapod.core.event.FavoritesEvent;
|
import de.danoeh.antennapod.core.event.FavoritesEvent;
|
||||||
import de.danoeh.antennapod.core.event.PlaybackPositionEvent;
|
import de.danoeh.antennapod.core.event.PlaybackPositionEvent;
|
||||||
|
import de.danoeh.antennapod.core.feed.Chapter;
|
||||||
import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent;
|
import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent;
|
||||||
import de.danoeh.antennapod.core.feed.FeedItem;
|
import de.danoeh.antennapod.core.feed.FeedItem;
|
||||||
import de.danoeh.antennapod.core.feed.FeedMedia;
|
import de.danoeh.antennapod.core.feed.FeedMedia;
|
||||||
|
@ -46,6 +47,7 @@ import de.danoeh.antennapod.dialog.SkipPreferenceDialog;
|
||||||
import de.danoeh.antennapod.dialog.SleepTimerDialog;
|
import de.danoeh.antennapod.dialog.SleepTimerDialog;
|
||||||
import de.danoeh.antennapod.dialog.VariableSpeedDialog;
|
import de.danoeh.antennapod.dialog.VariableSpeedDialog;
|
||||||
import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler;
|
import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler;
|
||||||
|
import de.danoeh.antennapod.view.ChapterSeekBar;
|
||||||
import de.danoeh.antennapod.ui.common.PlaybackSpeedIndicatorView;
|
import de.danoeh.antennapod.ui.common.PlaybackSpeedIndicatorView;
|
||||||
import io.reactivex.Maybe;
|
import io.reactivex.Maybe;
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
|
@ -63,7 +65,7 @@ import java.util.List;
|
||||||
* Shows the audio player.
|
* Shows the audio player.
|
||||||
*/
|
*/
|
||||||
public class AudioPlayerFragment extends Fragment implements
|
public class AudioPlayerFragment extends Fragment implements
|
||||||
SeekBar.OnSeekBarChangeListener, Toolbar.OnMenuItemClickListener {
|
ChapterSeekBar.OnSeekBarChangeListener, Toolbar.OnMenuItemClickListener {
|
||||||
public static final String TAG = "AudioPlayerFragment";
|
public static final String TAG = "AudioPlayerFragment";
|
||||||
private static final int POS_COVER = 0;
|
private static final int POS_COVER = 0;
|
||||||
private static final int POS_DESCR = 1;
|
private static final int POS_DESCR = 1;
|
||||||
|
@ -77,7 +79,7 @@ public class AudioPlayerFragment extends Fragment implements
|
||||||
private ViewPager2 pager;
|
private ViewPager2 pager;
|
||||||
private TextView txtvPosition;
|
private TextView txtvPosition;
|
||||||
private TextView txtvLength;
|
private TextView txtvLength;
|
||||||
private SeekBar sbPosition;
|
private ChapterSeekBar sbPosition;
|
||||||
private ImageButton butRev;
|
private ImageButton butRev;
|
||||||
private TextView txtvRev;
|
private TextView txtvRev;
|
||||||
private ImageButton butPlay;
|
private ImageButton butPlay;
|
||||||
|
@ -172,12 +174,33 @@ public class AudioPlayerFragment extends Fragment implements
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setHasChapters(boolean hasChapters) {
|
private void setHasChapters(boolean hasChapters) {
|
||||||
this.hasChapters = hasChapters;
|
this.hasChapters = hasChapters;
|
||||||
tabLayoutMediator.detach();
|
tabLayoutMediator.detach();
|
||||||
tabLayoutMediator.attach();
|
tabLayoutMediator.attach();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setChapterDividers(Playable media) {
|
||||||
|
|
||||||
|
if (media == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float[] dividerPos = null;
|
||||||
|
|
||||||
|
if (hasChapters) {
|
||||||
|
List<Chapter> chapters = media.getChapters();
|
||||||
|
dividerPos = new float[chapters.size()];
|
||||||
|
float duration = media.getDuration();
|
||||||
|
|
||||||
|
for (int i = 0; i < chapters.size(); i++) {
|
||||||
|
dividerPos[i] = chapters.get(i).getStart() / duration;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sbPosition.setDividerPos(dividerPos);
|
||||||
|
}
|
||||||
|
|
||||||
public View getExternalPlayerHolder() {
|
public View getExternalPlayerHolder() {
|
||||||
return getView().findViewById(R.id.playerFragment);
|
return getView().findViewById(R.id.playerFragment);
|
||||||
}
|
}
|
||||||
|
@ -298,16 +321,17 @@ public class AudioPlayerFragment extends Fragment implements
|
||||||
disposable = Maybe.create(emitter -> {
|
disposable = Maybe.create(emitter -> {
|
||||||
Playable media = controller.getMedia();
|
Playable media = controller.getMedia();
|
||||||
if (media != null) {
|
if (media != null) {
|
||||||
|
media.loadChapterMarks(getContext());
|
||||||
emitter.onSuccess(media);
|
emitter.onSuccess(media);
|
||||||
} else {
|
} else {
|
||||||
emitter.onComplete();
|
emitter.onComplete();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(media -> updateUi((Playable) media),
|
.subscribe(media -> updateUi((Playable) media),
|
||||||
error -> Log.e(TAG, Log.getStackTraceString(error)),
|
error -> Log.e(TAG, Log.getStackTraceString(error)),
|
||||||
() -> updateUi(null));
|
() -> updateUi(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
private PlaybackController newPlaybackController() {
|
private PlaybackController newPlaybackController() {
|
||||||
|
@ -389,8 +413,15 @@ public class AudioPlayerFragment extends Fragment implements
|
||||||
if (controller == null) {
|
if (controller == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (media != null && media.getChapters() != null) {
|
||||||
|
setHasChapters(media.getChapters().size() > 0);
|
||||||
|
} else {
|
||||||
|
setHasChapters(false);
|
||||||
|
}
|
||||||
updatePosition(new PlaybackPositionEvent(controller.getPosition(), controller.getDuration()));
|
updatePosition(new PlaybackPositionEvent(controller.getPosition(), controller.getDuration()));
|
||||||
updatePlaybackSpeedButton(media);
|
updatePlaybackSpeedButton(media);
|
||||||
|
setChapterDividers(media);
|
||||||
setupOptionsMenu(media);
|
setupOptionsMenu(media);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -137,7 +137,6 @@ public class ChaptersFragment extends Fragment {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
adapter.setMedia(media);
|
adapter.setMedia(media);
|
||||||
((AudioPlayerFragment) getParentFragment()).setHasChapters(adapter.getItemCount() > 0);
|
|
||||||
int positionOfCurrentChapter = getCurrentChapter(media);
|
int positionOfCurrentChapter = getCurrentChapter(media);
|
||||||
updateChapterSelection(positionOfCurrentChapter);
|
updateChapterSelection(positionOfCurrentChapter);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,129 @@
|
||||||
|
package de.danoeh.antennapod.view;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import de.danoeh.antennapod.ui.common.ThemeUtils;
|
||||||
|
|
||||||
|
public class ChapterSeekBar extends androidx.appcompat.widget.AppCompatSeekBar {
|
||||||
|
|
||||||
|
private float top;
|
||||||
|
private float width;
|
||||||
|
private float bottom;
|
||||||
|
private float density;
|
||||||
|
private float progressPrimary;
|
||||||
|
private float progressSecondary;
|
||||||
|
private float[] dividerPos;
|
||||||
|
private final Paint paintBackground = new Paint();
|
||||||
|
private final Paint paintProgressPrimary = new Paint();
|
||||||
|
private final Paint paintProgressSecondary = new Paint();
|
||||||
|
|
||||||
|
public ChapterSeekBar(Context context) {
|
||||||
|
super(context);
|
||||||
|
init(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChapterSeekBar(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
init(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChapterSeekBar(Context context, AttributeSet attrs, int defStyle) {
|
||||||
|
super(context, attrs, defStyle);
|
||||||
|
init(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init(Context context) {
|
||||||
|
setBackground(null); // Removes the thumb shadow
|
||||||
|
dividerPos = null;
|
||||||
|
density = context.getResources().getDisplayMetrics().density;
|
||||||
|
paintBackground.setColor(ThemeUtils.getColorFromAttr(getContext(),
|
||||||
|
de.danoeh.antennapod.core.R.attr.currently_playing_background));
|
||||||
|
paintBackground.setAlpha(128);
|
||||||
|
paintProgressPrimary.setColor(ThemeUtils.getColorFromAttr(getContext(),
|
||||||
|
de.danoeh.antennapod.core.R.attr.colorPrimary));
|
||||||
|
paintProgressSecondary.setColor(ThemeUtils.getColorFromAttr(getContext(),
|
||||||
|
de.danoeh.antennapod.core.R.attr.seek_background));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the relative positions of the chapter dividers.
|
||||||
|
* @param dividerPos of the chapter dividers relative to the duration of the media.
|
||||||
|
*/
|
||||||
|
public void setDividerPos(final float[] dividerPos) {
|
||||||
|
if (dividerPos != null) {
|
||||||
|
this.dividerPos = new float[dividerPos.length + 2];
|
||||||
|
this.dividerPos[0] = 0;
|
||||||
|
System.arraycopy(dividerPos, 0, this.dividerPos, 1, dividerPos.length);
|
||||||
|
this.dividerPos[this.dividerPos.length - 1] = 1;
|
||||||
|
} else {
|
||||||
|
this.dividerPos = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected synchronized void onDraw(Canvas canvas) {
|
||||||
|
top = getTop() + density * 7.5f;
|
||||||
|
bottom = getBottom() - density * 7.5f;
|
||||||
|
width = (float) (getRight() - getPaddingRight() - getLeft() - getPaddingLeft());
|
||||||
|
progressSecondary = getSecondaryProgress() / (float) getMax() * width;
|
||||||
|
progressPrimary = getProgress() / (float) getMax() * width;
|
||||||
|
|
||||||
|
if (dividerPos == null) {
|
||||||
|
drawProgress(canvas);
|
||||||
|
} else {
|
||||||
|
drawProgressChapters(canvas);
|
||||||
|
}
|
||||||
|
drawThumb(canvas);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawProgress(Canvas canvas) {
|
||||||
|
final int saveCount = canvas.save();
|
||||||
|
canvas.translate(getPaddingLeft(), getPaddingTop());
|
||||||
|
canvas.drawRect(0, top, width, bottom, paintBackground);
|
||||||
|
canvas.drawRect(0, top, progressSecondary, bottom, paintProgressSecondary);
|
||||||
|
canvas.drawRect(0, top, progressPrimary, bottom, paintProgressPrimary);
|
||||||
|
canvas.restoreToCount(saveCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawProgressChapters(Canvas canvas) {
|
||||||
|
final int saveCount = canvas.save();
|
||||||
|
int currChapter = 1;
|
||||||
|
float chapterMargin = density * 0.6f;
|
||||||
|
float topExpanded = getTop() + density * 7;
|
||||||
|
float bottomExpanded = getBottom() - density * 7;
|
||||||
|
|
||||||
|
canvas.translate(getPaddingLeft(), getPaddingTop());
|
||||||
|
|
||||||
|
for (int i = 1; i < dividerPos.length; i++) {
|
||||||
|
float right = dividerPos[i] * width - chapterMargin;
|
||||||
|
float left = dividerPos[i - 1] * width + chapterMargin;
|
||||||
|
float rightCurr = dividerPos[currChapter] * width - chapterMargin;
|
||||||
|
float leftCurr = dividerPos[currChapter - 1] * width + chapterMargin;
|
||||||
|
|
||||||
|
canvas.drawRect(left, top, right, bottom, paintBackground);
|
||||||
|
|
||||||
|
if (right < progressPrimary) {
|
||||||
|
currChapter = i + 1;
|
||||||
|
canvas.drawRect(left, top, right, bottom, paintProgressPrimary);
|
||||||
|
} else if (isPressed()) {
|
||||||
|
canvas.drawRect(leftCurr, topExpanded, rightCurr, bottomExpanded, paintBackground);
|
||||||
|
canvas.drawRect(leftCurr, topExpanded, progressPrimary, bottomExpanded, paintProgressPrimary);
|
||||||
|
} else {
|
||||||
|
if (progressSecondary > leftCurr) {
|
||||||
|
canvas.drawRect(leftCurr, top, progressSecondary, bottom, paintProgressSecondary);
|
||||||
|
}
|
||||||
|
canvas.drawRect(leftCurr, top, progressPrimary, bottom, paintProgressPrimary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
canvas.restoreToCount(saveCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawThumb(Canvas canvas) {
|
||||||
|
final int saveCount = canvas.save();
|
||||||
|
canvas.translate(getPaddingLeft() - getThumbOffset(), getPaddingTop());
|
||||||
|
getThumb().draw(canvas);
|
||||||
|
canvas.restoreToCount(saveCount);
|
||||||
|
}
|
||||||
|
}
|
|
@ -88,7 +88,7 @@
|
||||||
android:layoutDirection="ltr"
|
android:layoutDirection="ltr"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<SeekBar
|
<de.danoeh.antennapod.view.ChapterSeekBar
|
||||||
android:id="@+id/sbPosition"
|
android:id="@+id/sbPosition"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
|
Loading…
Reference in New Issue