From 7fd2ebc25293bc0f083e65de23c082ce703cf359 Mon Sep 17 00:00:00 2001 From: Nathan Schulzke Date: Mon, 26 Jul 2021 20:51:41 -0600 Subject: [PATCH 1/7] Add ability to mark an item as played --- .../schabi/newpipe/local/feed/FeedFragment.kt | 6 ++-- .../local/history/HistoryRecordManager.java | 36 +++++++++++++++++++ .../newpipe/util/StreamDialogEntry.java | 13 ++++++- app/src/main/res/values/strings.xml | 1 + 4 files changed, 53 insertions(+), 3 deletions(-) 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..ab32e5fbb 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 @@ -331,7 +331,8 @@ class FeedFragment : BaseStateFragment() { StreamDialogEntry.start_here_on_background, StreamDialogEntry.append_playlist, StreamDialogEntry.share, - StreamDialogEntry.open_in_browser + StreamDialogEntry.open_in_browser, + StreamDialogEntry.mark_as_played ) ) } else { @@ -341,7 +342,8 @@ class FeedFragment : BaseStateFragment() { StreamDialogEntry.start_here_on_popup, StreamDialogEntry.append_playlist, StreamDialogEntry.share, - StreamDialogEntry.open_in_browser + StreamDialogEntry.open_in_browser, + StreamDialogEntry.mark_as_played ) ) } 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..3d151bad8 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 @@ -42,6 +42,7 @@ 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.player.playqueue.PlayQueueItem; import java.time.OffsetDateTime; @@ -81,6 +82,41 @@ public class HistoryRecordManager { // Watch History /////////////////////////////////////////////////////// + public Maybe markAsPlayed(final StreamInfoItem info) { + if (!isStreamHistoryEnabled()) { + return Maybe.empty(); + } + + final OffsetDateTime currentTime = OffsetDateTime.now(ZoneOffset.UTC); + return Maybe.fromCallable(() -> database.runInTransaction(() -> { + final long streamId = streamTable.upsert(new StreamEntity(info)); + + final List states = streamStateTable.getState(streamId) + .blockingFirst(); + if (!states.isEmpty()) { + final StreamStateEntity entity = states.get(0); + entity.setProgressMillis(info.getDuration() * 1000); + streamStateTable.update(entity); + } else { + final StreamStateEntity entity = new StreamStateEntity( + streamId, + info.getDuration() * 1000 + ); + streamStateTable.insert(entity); + } + + 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..828e4ebfc 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_played(R.string.mark_as_played, (fragment, item) -> { + new HistoryRecordManager(fragment.getContext()) + .markAsPlayed(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..f8c7f4515 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 played Open in popup mode Open with Share From 2b41f710a8cfa2ce8237de4a36bcba4b13dca78c Mon Sep 17 00:00:00 2001 From: Nathan Schulzke Date: Tue, 27 Jul 2021 13:26:51 -0600 Subject: [PATCH 2/7] Change played to watched --- .../main/java/org/schabi/newpipe/local/feed/FeedFragment.kt | 4 ++-- .../schabi/newpipe/local/history/HistoryRecordManager.java | 2 +- .../main/java/org/schabi/newpipe/util/StreamDialogEntry.java | 4 ++-- app/src/main/res/values/strings.xml | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) 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 ab32e5fbb..85fea2efe 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 @@ -332,7 +332,7 @@ class FeedFragment : BaseStateFragment() { StreamDialogEntry.append_playlist, StreamDialogEntry.share, StreamDialogEntry.open_in_browser, - StreamDialogEntry.mark_as_played + StreamDialogEntry.mark_as_watched ) ) } else { @@ -343,7 +343,7 @@ class FeedFragment : BaseStateFragment() { StreamDialogEntry.append_playlist, StreamDialogEntry.share, StreamDialogEntry.open_in_browser, - StreamDialogEntry.mark_as_played + StreamDialogEntry.mark_as_watched ) ) } 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 3d151bad8..ad1b695cb 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 @@ -82,7 +82,7 @@ public class HistoryRecordManager { // Watch History /////////////////////////////////////////////////////// - public Maybe markAsPlayed(final StreamInfoItem info) { + public Maybe markAsWatched(final StreamInfoItem 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 828e4ebfc..89b48c9a7 100644 --- a/app/src/main/java/org/schabi/newpipe/util/StreamDialogEntry.java +++ b/app/src/main/java/org/schabi/newpipe/util/StreamDialogEntry.java @@ -98,9 +98,9 @@ public enum StreamDialogEntry { ShareUtils.openUrlInBrowser(fragment.getContext(), item.getUrl())), - mark_as_played(R.string.mark_as_played, (fragment, item) -> { + mark_as_watched(R.string.mark_as_watched, (fragment, item) -> { new HistoryRecordManager(fragment.getContext()) - .markAsPlayed(item) + .markAsWatched(item) .onErrorComplete() .observeOn(AndroidSchedulers.mainThread()) .subscribe(); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f8c7f4515..dcef62110 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -9,7 +9,7 @@ Cancel https://f-droid.org/repository/browse/?fdfilter=vlc&fdid=org.videolan.vlc Open in browser - Mark as played + Mark as watched Open in popup mode Open with Share @@ -710,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 From f451bdbfa4450ff8fb5afc5bdc1555a6b5f41032 Mon Sep 17 00:00:00 2001 From: Nathan Schulzke Date: Tue, 27 Jul 2021 13:26:51 -0600 Subject: [PATCH 3/7] Do not add Mark as Watched to a live stream. --- .../org/schabi/newpipe/local/feed/FeedFragment.kt | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) 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 85fea2efe..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 @@ -331,8 +331,7 @@ class FeedFragment : BaseStateFragment() { StreamDialogEntry.start_here_on_background, StreamDialogEntry.append_playlist, StreamDialogEntry.share, - StreamDialogEntry.open_in_browser, - StreamDialogEntry.mark_as_watched + StreamDialogEntry.open_in_browser ) ) } else { @@ -342,11 +341,15 @@ class FeedFragment : BaseStateFragment() { StreamDialogEntry.start_here_on_popup, StreamDialogEntry.append_playlist, StreamDialogEntry.share, - StreamDialogEntry.open_in_browser, - StreamDialogEntry.mark_as_watched + StreamDialogEntry.open_in_browser ) ) } + 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 -> From 4c632810ec8e21544885cf3e564889782ef06b5b Mon Sep 17 00:00:00 2001 From: Nathan Schulzke Date: Tue, 27 Jul 2021 15:20:43 -0600 Subject: [PATCH 4/7] Fetch the stream info via a network request if no duration is found when attempting to mark as watched. --- .../local/history/HistoryRecordManager.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) 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 ad1b695cb..417492005 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 @@ -44,6 +44,7 @@ import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.player.playqueue.PlayQueueItem; +import org.schabi.newpipe.util.ExtractorHelper; import java.time.OffsetDateTime; import java.time.ZoneOffset; @@ -91,16 +92,27 @@ public class HistoryRecordManager { return Maybe.fromCallable(() -> database.runInTransaction(() -> { final long streamId = streamTable.upsert(new StreamEntity(info)); + long duration = info.getDuration(); + if (duration < 0) { + duration = ExtractorHelper.getStreamInfo( + info.getServiceId(), + info.getUrl(), + false) + .subscribeOn(Schedulers.io()) + .blockingGet() + .getDuration(); + } + final List states = streamStateTable.getState(streamId) .blockingFirst(); if (!states.isEmpty()) { final StreamStateEntity entity = states.get(0); - entity.setProgressMillis(info.getDuration() * 1000); + entity.setProgressMillis(duration * 1000); streamStateTable.update(entity); } else { final StreamStateEntity entity = new StreamStateEntity( streamId, - info.getDuration() * 1000 + duration * 1000 ); streamStateTable.insert(entity); } From bdd16e06e064d8fbc5f207ca0fa89ae9aff4f4bd Mon Sep 17 00:00:00 2001 From: Nathan Schulzke Date: Wed, 28 Jul 2021 08:25:39 -0600 Subject: [PATCH 5/7] Add comments describing the purpose of the markAsWatched method --- .../local/history/HistoryRecordManager.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) 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 417492005..eb5aab12c 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; @@ -43,6 +44,7 @@ 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; @@ -83,6 +85,15 @@ 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(); @@ -93,6 +104,7 @@ public class HistoryRecordManager { final long streamId = streamTable.upsert(new StreamEntity(info)); long duration = info.getDuration(); + // Duration will not exist if the item was loaded with fast mode, so fetch it if empty if (duration < 0) { duration = ExtractorHelper.getStreamInfo( info.getServiceId(), @@ -103,6 +115,7 @@ public class HistoryRecordManager { .getDuration(); } + // Update the stream progress to the full duration of the video final List states = streamStateTable.getState(streamId) .blockingFirst(); if (!states.isEmpty()) { @@ -117,6 +130,7 @@ public class HistoryRecordManager { streamStateTable.insert(entity); } + // Add a history entry final StreamHistoryEntity latestEntry = streamHistoryTable.getLatestEntry(streamId); if (latestEntry != null) { streamHistoryTable.delete(latestEntry); From 0e12cdea7c1d5687d662aad76aef84762e161491 Mon Sep 17 00:00:00 2001 From: Nathan Schulzke Date: Thu, 29 Jul 2021 20:59:23 -0600 Subject: [PATCH 6/7] Save the fetched duration to the database so that it can render the view correctly. --- .../local/history/HistoryRecordManager.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) 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 eb5aab12c..29a8c6177 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 @@ -101,31 +101,31 @@ public class HistoryRecordManager { final OffsetDateTime currentTime = OffsetDateTime.now(ZoneOffset.UTC); return Maybe.fromCallable(() -> database.runInTransaction(() -> { - final long streamId = streamTable.upsert(new StreamEntity(info)); - - long duration = info.getDuration(); // Duration will not exist if the item was loaded with fast mode, so fetch it if empty - if (duration < 0) { - duration = ExtractorHelper.getStreamInfo( + if (info.getDuration() < 0) { + final long duration = ExtractorHelper.getStreamInfo( info.getServiceId(), info.getUrl(), false) .subscribeOn(Schedulers.io()) .blockingGet() .getDuration(); + info.setDuration(duration); } + // Upsert to get a stream ID and update durations if we fetched one + final long 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); + entity.setProgressMillis(info.getDuration() * 1000); streamStateTable.update(entity); } else { final StreamStateEntity entity = new StreamStateEntity( streamId, - duration * 1000 + info.getDuration() * 1000 ); streamStateTable.insert(entity); } From dc0a28b93d2d9a71b49f211e7802f62456bb24d0 Mon Sep 17 00:00:00 2001 From: Nathan Schulzke Date: Sat, 31 Jul 2021 09:45:45 -0600 Subject: [PATCH 7/7] Upsert the complete info if we fetch it for marking as watched --- .../local/history/HistoryRecordManager.java | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) 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 29a8c6177..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 @@ -101,31 +101,35 @@ public class HistoryRecordManager { 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 long duration = ExtractorHelper.getStreamInfo( + final StreamInfo completeInfo = ExtractorHelper.getStreamInfo( info.getServiceId(), info.getUrl(), - false) + false + ) .subscribeOn(Schedulers.io()) - .blockingGet() - .getDuration(); - info.setDuration(duration); + .blockingGet(); + duration = completeInfo.getDuration(); + streamId = streamTable.upsert(new StreamEntity(completeInfo)); + } else { + duration = info.getDuration(); + streamId = streamTable.upsert(new StreamEntity(info)); } - // Upsert to get a stream ID and update durations if we fetched one - final long 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(info.getDuration() * 1000); + entity.setProgressMillis(duration * 1000); streamStateTable.update(entity); } else { final StreamStateEntity entity = new StreamStateEntity( streamId, - info.getDuration() * 1000 + duration * 1000 ); streamStateTable.insert(entity); }