diff --git a/app/src/main/java/org/schabi/newpipe/database/feed/dao/FeedDAO.kt b/app/src/main/java/org/schabi/newpipe/database/feed/dao/FeedDAO.kt index f3a4a13b5..4b3bcdd7e 100644 --- a/app/src/main/java/org/schabi/newpipe/database/feed/dao/FeedDAO.kt +++ b/app/src/main/java/org/schabi/newpipe/database/feed/dao/FeedDAO.kt @@ -10,6 +10,7 @@ import io.reactivex.rxjava3.core.Flowable import org.schabi.newpipe.database.feed.model.FeedEntity import org.schabi.newpipe.database.feed.model.FeedLastUpdatedEntity import org.schabi.newpipe.database.stream.StreamWithState +import org.schabi.newpipe.database.stream.model.StreamStateEntity import org.schabi.newpipe.database.subscription.SubscriptionEntity import java.time.OffsetDateTime @@ -79,6 +80,9 @@ abstract class FeedDAO { WHERE ( sh.stream_id IS NULL + OR sst.stream_id IS NULL + OR sst.progress_time < s.duration * 1000 - ${StreamStateEntity.PLAYBACK_FINISHED_END_MILLISECONDS} + OR sst.progress_time < s.duration * 1000 * 3 / 4 OR s.stream_type = 'LIVE_STREAM' OR s.stream_type = 'AUDIO_LIVE_STREAM' ) diff --git a/app/src/main/java/org/schabi/newpipe/database/stream/model/StreamStateEntity.java b/app/src/main/java/org/schabi/newpipe/database/stream/model/StreamStateEntity.java index 1ce834a82..fd8dc0a42 100644 --- a/app/src/main/java/org/schabi/newpipe/database/stream/model/StreamStateEntity.java +++ b/app/src/main/java/org/schabi/newpipe/database/stream/model/StreamStateEntity.java @@ -5,8 +5,6 @@ import androidx.room.ColumnInfo; import androidx.room.Entity; import androidx.room.ForeignKey; -import java.util.concurrent.TimeUnit; - import static androidx.room.ForeignKey.CASCADE; import static org.schabi.newpipe.database.stream.model.StreamStateEntity.JOIN_STREAM_ID; import static org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_STATE_TABLE; @@ -30,11 +28,13 @@ public class StreamStateEntity { /** * Playback state will not be saved, if playback time is less than this threshold. */ - private static final int PLAYBACK_SAVE_THRESHOLD_START_SECONDS = 5; + private static final long PLAYBACK_SAVE_THRESHOLD_START_MILLISECONDS = 5000; // 5000ms = 5s + /** - * Playback state will not be saved, if time left is less than this threshold. + * @see #isFinished(long) + * @see org.schabi.newpipe.database.feed.dao.FeedDAO#getLiveOrNotPlayedStreams() */ - private static final int PLAYBACK_SAVE_THRESHOLD_END_SECONDS = 10; + public static final long PLAYBACK_FINISHED_END_MILLISECONDS = 60000; // 60000ms = 60s @ColumnInfo(name = JOIN_STREAM_ID) private long streamUid; @@ -63,10 +63,27 @@ public class StreamStateEntity { this.progressTime = progressTime; } - public boolean isValid(final int durationInSeconds) { - final int seconds = (int) TimeUnit.MILLISECONDS.toSeconds(progressTime); - return seconds > PLAYBACK_SAVE_THRESHOLD_START_SECONDS - && seconds < durationInSeconds - PLAYBACK_SAVE_THRESHOLD_END_SECONDS; + /** + * The state will be considered valid, and thus be saved, if the progress is more than {@link + * #PLAYBACK_SAVE_THRESHOLD_START_MILLISECONDS}. + * @return whether this stream state entity should be saved or not + */ + public boolean isValid() { + return progressTime > PLAYBACK_SAVE_THRESHOLD_START_MILLISECONDS; + } + + /** + * The video will be considered as finished, if the time left is less than {@link + * #PLAYBACK_FINISHED_END_MILLISECONDS} and the progress is at least 3/4 of the video length. + * The state will be saved anyway, so that it can be shown under stream info items, but the + * player will not resume if a state is considered as finished. Finished streams are also the + * ones that can be filtered out in the feed fragment. + * @param durationInSeconds the duration of the stream connected with this state, in seconds + * @return whether the stream is finished or not + */ + public boolean isFinished(final long durationInSeconds) { + return progressTime >= durationInSeconds * 1000 - PLAYBACK_FINISHED_END_MILLISECONDS + && progressTime >= durationInSeconds * 1000 * 3 / 4; } @Override diff --git a/app/src/main/java/org/schabi/newpipe/local/history/HistoryRecordManager.java b/app/src/main/java/org/schabi/newpipe/local/history/HistoryRecordManager.java index 66f1bda0e..a0bd2f479 100644 --- a/app/src/main/java/org/schabi/newpipe/local/history/HistoryRecordManager.java +++ b/app/src/main/java/org/schabi/newpipe/local/history/HistoryRecordManager.java @@ -215,7 +215,7 @@ public class HistoryRecordManager { .flatMapPublisher(streamStateTable::getState) .firstElement() .flatMap(list -> list.isEmpty() ? Maybe.empty() : Maybe.just(list.get(0))) - .filter(state -> state.isValid((int) queueItem.getDuration())) + .filter(StreamStateEntity::isValid) .subscribeOn(Schedulers.io()); } @@ -224,7 +224,7 @@ public class HistoryRecordManager { .flatMapPublisher(streamStateTable::getState) .firstElement() .flatMap(list -> list.isEmpty() ? Maybe.empty() : Maybe.just(list.get(0))) - .filter(state -> state.isValid((int) info.getDuration())) + .filter(StreamStateEntity::isValid) .subscribeOn(Schedulers.io()); } @@ -232,7 +232,7 @@ public class HistoryRecordManager { return Completable.fromAction(() -> database.runInTransaction(() -> { final long streamId = streamTable.upsert(new StreamEntity(info)); final StreamStateEntity state = new StreamStateEntity(streamId, progressTime); - if (state.isValid((int) info.getDuration())) { + if (state.isValid()) { streamStateTable.upsert(state); } else { streamStateTable.deleteState(streamId); diff --git a/app/src/main/java/org/schabi/newpipe/player/Player.java b/app/src/main/java/org/schabi/newpipe/player/Player.java index 0c5dbbb6f..55e2e2dd7 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -671,7 +671,11 @@ public final class Player implements //.doFinally() .subscribe( state -> { - newQueue.setRecovery(newQueue.getIndex(), state.getProgressTime()); + if (!state.isFinished(newQueue.getItem().getDuration())) { + // resume playback only if the stream was not played to the end + newQueue.setRecovery(newQueue.getIndex(), + state.getProgressTime()); + } initPlayback(newQueue, repeatMode, playbackSpeed, playbackPitch, playbackSkipSilence, playWhenReady, isMuted); },