diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt index 03d39b13c..815418895 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt @@ -345,6 +345,11 @@ class FeedFragment : BaseStateFragment() { ) ) } + if (item.streamType != StreamType.AUDIO_LIVE_STREAM && item.streamType != StreamType.LIVE_STREAM) { + entries.add( + StreamDialogEntry.mark_as_watched + ) + } StreamDialogEntry.setEnabledEntries(entries) InfoItemDialog(activity, item, StreamDialogEntry.getCommands(context)) { _, which -> 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 38ebe504e..823e56d9e 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 @@ -28,6 +28,7 @@ import org.schabi.newpipe.NewPipeDatabase; import org.schabi.newpipe.R; import org.schabi.newpipe.database.AppDatabase; import org.schabi.newpipe.database.LocalItem; +import org.schabi.newpipe.database.feed.dao.FeedDAO; import org.schabi.newpipe.database.history.dao.SearchHistoryDAO; import org.schabi.newpipe.database.history.dao.StreamHistoryDAO; import org.schabi.newpipe.database.history.model.SearchHistoryEntry; @@ -42,7 +43,10 @@ import org.schabi.newpipe.database.stream.model.StreamEntity; import org.schabi.newpipe.database.stream.model.StreamStateEntity; import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.stream.StreamInfo; +import org.schabi.newpipe.extractor.stream.StreamInfoItem; +import org.schabi.newpipe.local.feed.FeedViewModel; import org.schabi.newpipe.player.playqueue.PlayQueueItem; +import org.schabi.newpipe.util.ExtractorHelper; import java.time.OffsetDateTime; import java.time.ZoneOffset; @@ -81,6 +85,68 @@ public class HistoryRecordManager { // Watch History /////////////////////////////////////////////////////// + /** + * Marks a stream item as watched such that it is hidden from the feed if watched videos are + * hidden. Adds a history entry and updates the stream progress to 100%. + * + * @see FeedDAO#getLiveOrNotPlayedStreams + * @see FeedViewModel#togglePlayedItems + * @param info the item to mark as watched + * @return a Maybe containing the ID of the item if successful + */ + public Maybe markAsWatched(final StreamInfoItem info) { + if (!isStreamHistoryEnabled()) { + return Maybe.empty(); + } + + final OffsetDateTime currentTime = OffsetDateTime.now(ZoneOffset.UTC); + return Maybe.fromCallable(() -> database.runInTransaction(() -> { + final long streamId; + final long duration; + // Duration will not exist if the item was loaded with fast mode, so fetch it if empty + if (info.getDuration() < 0) { + final StreamInfo completeInfo = ExtractorHelper.getStreamInfo( + info.getServiceId(), + info.getUrl(), + false + ) + .subscribeOn(Schedulers.io()) + .blockingGet(); + duration = completeInfo.getDuration(); + streamId = streamTable.upsert(new StreamEntity(completeInfo)); + } else { + duration = info.getDuration(); + streamId = streamTable.upsert(new StreamEntity(info)); + } + + // Update the stream progress to the full duration of the video + final List states = streamStateTable.getState(streamId) + .blockingFirst(); + if (!states.isEmpty()) { + final StreamStateEntity entity = states.get(0); + entity.setProgressMillis(duration * 1000); + streamStateTable.update(entity); + } else { + final StreamStateEntity entity = new StreamStateEntity( + streamId, + duration * 1000 + ); + streamStateTable.insert(entity); + } + + // Add a history entry + final StreamHistoryEntity latestEntry = streamHistoryTable.getLatestEntry(streamId); + if (latestEntry != null) { + streamHistoryTable.delete(latestEntry); + latestEntry.setAccessDate(currentTime); + latestEntry.setRepeatCount(latestEntry.getRepeatCount() + 1); + return streamHistoryTable.insert(latestEntry); + } else { + return streamHistoryTable.insert(new StreamHistoryEntity(streamId, currentTime)); + } + })).subscribeOn(Schedulers.io()); + } + public Maybe onViewed(final StreamInfo info) { if (!isStreamHistoryEnabled()) { return Maybe.empty(); diff --git a/app/src/main/java/org/schabi/newpipe/util/StreamDialogEntry.java b/app/src/main/java/org/schabi/newpipe/util/StreamDialogEntry.java index 8bfd428b8..89b48c9a7 100644 --- a/app/src/main/java/org/schabi/newpipe/util/StreamDialogEntry.java +++ b/app/src/main/java/org/schabi/newpipe/util/StreamDialogEntry.java @@ -9,6 +9,7 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.local.dialog.PlaylistAppendDialog; import org.schabi.newpipe.local.dialog.PlaylistCreationDialog; +import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.player.MainPlayer; import org.schabi.newpipe.player.helper.PlayerHolder; import org.schabi.newpipe.player.playqueue.SinglePlayQueue; @@ -18,6 +19,8 @@ import org.schabi.newpipe.util.external_communication.ShareUtils; import java.util.Collections; import java.util.List; +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; + import static org.schabi.newpipe.player.MainPlayer.PlayerType.AUDIO; import static org.schabi.newpipe.player.MainPlayer.PlayerType.POPUP; @@ -92,9 +95,17 @@ public enum StreamDialogEntry { item.getThumbnailUrl())), open_in_browser(R.string.open_in_browser, (fragment, item) -> - ShareUtils.openUrlInBrowser(fragment.getContext(), item.getUrl())); + ShareUtils.openUrlInBrowser(fragment.getContext(), item.getUrl())), + mark_as_watched(R.string.mark_as_watched, (fragment, item) -> { + new HistoryRecordManager(fragment.getContext()) + .markAsWatched(item) + .onErrorComplete() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(); + }); + /////////////// // variables // /////////////// diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f6d0246dd..dcef62110 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -9,6 +9,7 @@ Cancel https://f-droid.org/repository/browse/?fdfilter=vlc&fdid=org.videolan.vlc Open in browser + Mark as watched Open in popup mode Open with Share @@ -709,7 +710,7 @@ Enable fast mode Disable fast mode Do you think feed loading is too slow? If so, try enabling fast loading (you can change it in settings or by pressing the button below).\n\nNewPipe offers two feed loading strategies:\n• Fetching the whole subscription channel, which is slow but complete.\n• Using a dedicated service endpoint, which is fast but usually not complete.\n\nThe difference between the two is that the fast one usually lacks some information, like the item\'s duration or type (can\'t distinguish between live videos and normal ones) and it may return less items.\n\nYouTube is an example of a service that offers this fast method with its RSS feed.\n\nSo the choice boils down to what you prefer: speed or precise information. - Show played items + Show watched items This content is not yet supported by NewPipe.\n\nIt will hopefully be supported in a future version. Channel\'s avatar thumbnail Created by %s