Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions NewPipeExtractor
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please revert this

Submodule NewPipeExtractor added at 3af732
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,11 @@ public interface SearchHistoryDAO extends HistoryDAO<SearchHistoryEntry> {
@Query("SELECT " + SEARCH + " FROM " + TABLE_NAME + " WHERE " + SEARCH + " LIKE :query || '%'"
+ " GROUP BY " + SEARCH + ORDER_BY_MAX_CREATION_DATE + " LIMIT :limit")
Flowable<List<String>> getSimilarEntries(String query, int limit);


@Query("SELECT * FROM " + TABLE_NAME + ORDER_BY_CREATION_DATE)
List<SearchHistoryEntry> getAllEntries();

@androidx.room.Delete
void delete(SearchHistoryEntry entry);
}
Original file line number Diff line number Diff line change
@@ -1,30 +1,11 @@
package org.schabi.newpipe.local.history;

/*
* Copyright (C) Mauricio Colli 2018
* HistoryRecordManager.java is part of NewPipe.
*
* NewPipe is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* NewPipe is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/

import static org.schabi.newpipe.util.ExtractorHelper.getStreamInfo;

import android.content.Context;
import android.content.SharedPreferences;

import androidx.annotation.NonNull;
import androidx.collection.LongLongPair;
import androidx.preference.PreferenceManager;

import org.schabi.newpipe.NewPipeDatabase;
Expand Down Expand Up @@ -85,46 +66,27 @@ public HistoryRecordManager(final Context context) {
// 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 FeedViewModel#setSaveShowPlayedItems
* @param info the item to mark as watched
* @return a Maybe containing the ID of the item if successful
*/
public Completable markAsWatched(final StreamInfoItem info) {
if (!isStreamHistoryEnabled()) {
return Completable.complete();
}

final var remoteInfo = getStreamInfo(info.getServiceId(), info.getUrl(), false)
.map(item ->
new LongLongPair(item.getDuration(), streamTable.upsert(new StreamEntity(item))));

return Single.just(info)
.filter(item -> item.getDuration() >= 0)
.map(item ->
new LongLongPair(item.getDuration(), streamTable.upsert(new StreamEntity(item)))
)
.switchIfEmpty(remoteInfo)
.flatMapCompletable(pair -> Completable.fromRunnable(() -> {
final long duration = pair.getFirst();
final long streamId = pair.getSecond();
return getStreamInfo(info.getServiceId(), info.getUrl(), false)
.map(streamInfo -> {
long duration = streamInfo.getDuration();
long streamId = streamTable.upsert(new StreamEntity(streamInfo));

// Update the stream progress to the full duration of the video
final var entity = new StreamStateEntity(streamId, duration * 1000);
final StreamStateEntity entity = new StreamStateEntity(streamId, duration * 1000);
streamStateTable.upsert(entity);

// Add a history entry
final var latestEntry = streamHistoryTable.getLatestEntry(streamId);
final StreamHistoryEntity latestEntry = streamHistoryTable.getLatestEntry(streamId);
if (latestEntry == null) {
final var currentTime = OffsetDateTime.now(ZoneOffset.UTC);
// never actually viewed: add history entry but with 0 views
final var entry = new StreamHistoryEntity(streamId, currentTime, 0);
streamHistoryTable.insert(entry);
final OffsetDateTime currentTime = OffsetDateTime.now(ZoneOffset.UTC);
streamHistoryTable.insert(new StreamHistoryEntity(streamId, currentTime, 0));
}
}))
return true;
})
.ignoreElement()
.subscribeOn(Schedulers.io());
}

Expand All @@ -144,41 +106,11 @@ public Maybe<Long> onViewed(final StreamInfo info) {
latestEntry.setRepeatCount(latestEntry.getRepeatCount() + 1);
return streamHistoryTable.insert(latestEntry);
} else {
// just viewed for the first time: set 1 view
return streamHistoryTable.insert(new StreamHistoryEntity(streamId, currentTime, 1));
}
})).subscribeOn(Schedulers.io());
}

public Completable deleteStreamHistoryAndState(final long streamId) {
return Completable.fromAction(() -> {
streamStateTable.deleteState(streamId);
streamHistoryTable.deleteStreamHistory(streamId);
}).subscribeOn(Schedulers.io());
}

public Single<Integer> deleteWholeStreamHistory() {
return Single.fromCallable(streamHistoryTable::deleteAll)
.subscribeOn(Schedulers.io());
}

public Single<Integer> deleteCompleteStreamStateHistory() {
return Single.fromCallable(streamStateTable::deleteAll)
.subscribeOn(Schedulers.io());
}

public Flowable<List<StreamHistoryEntry>> getStreamHistorySortedById() {
return streamHistoryTable.getHistorySortedById().subscribeOn(Schedulers.io());
}

public Flowable<List<StreamStatisticsEntry>> getStreamStatistics() {
return streamHistoryTable.getStatistics().subscribeOn(Schedulers.io());
}

private boolean isStreamHistoryEnabled() {
return sharedPreferences.getBoolean(streamHistoryKey, false);
}

///////////////////////////////////////////////////////
// Search History
///////////////////////////////////////////////////////
Expand All @@ -197,94 +129,52 @@ public Maybe<Long> onSearched(final int serviceId, final String search) {
latestEntry.setCreationDate(currentTime);
return (long) searchHistoryTable.update(latestEntry);
} else {
return searchHistoryTable.insert(newEntry);
final long insertedId = searchHistoryTable.insert(newEntry);

boolean infiniteEnabled = sharedPreferences.getBoolean("infinite_search_history", false);
if (!infiniteEnabled) {
List<SearchHistoryEntry> allEntries = searchHistoryTable.getAllEntries();
int maxSize = 25;
if (allEntries.size() > maxSize) {
for (int i = maxSize; i < allEntries.size(); i++) {
searchHistoryTable.delete(allEntries.get(i));
}
}
}
return insertedId;
}
})).subscribeOn(Schedulers.io());
}

public Single<Integer> deleteSearchHistory(final String search) {
return Single.fromCallable(() -> searchHistoryTable.deleteAllWhereQuery(search))
.subscribeOn(Schedulers.io());
}
public Flowable<List<String>> getRelatedSearches(
final String query,
final int similarQueryLimit,
final int uniqueQueryLimit) {

public Single<Integer> deleteCompleteSearchHistory() {
return Single.fromCallable(searchHistoryTable::deleteAll)
.subscribeOn(Schedulers.io());
}
boolean infiniteEnabled = sharedPreferences.getBoolean("infinite_search_history", false);
int largeLimit = 100000;

public Flowable<List<String>> getRelatedSearches(final String query,
final int similarQueryLimit,
final int uniqueQueryLimit) {
return !query.isEmpty()
? searchHistoryTable.getSimilarEntries(query, similarQueryLimit)
: searchHistoryTable.getUniqueEntries(uniqueQueryLimit);
}
int sLimit = infiniteEnabled ? largeLimit : similarQueryLimit;
int uLimit = infiniteEnabled ? largeLimit : uniqueQueryLimit;

private boolean isSearchHistoryEnabled() {
return sharedPreferences.getBoolean(searchHistoryKey, false);
return query.isEmpty()
? searchHistoryTable.getUniqueEntries(uLimit)
: searchHistoryTable.getSimilarEntries(query, sLimit);
}

///////////////////////////////////////////////////////
// Stream State History
// Utility
///////////////////////////////////////////////////////

public Maybe<StreamStateEntity> loadStreamState(final PlayQueueItem queueItem) {
return queueItem.getStream()
.flatMapMaybe(this::loadStreamState)
.filter(state -> state.isValid(queueItem.getDuration()))
.subscribeOn(Schedulers.io());
}

public Maybe<StreamStateEntity> loadStreamState(final StreamInfo info) {
return Single.fromCallable(() -> streamTable.upsert(new StreamEntity(info)))
.flatMapMaybe(streamStateTable::getState)
.subscribeOn(Schedulers.io());
}

public Completable saveStreamState(@NonNull final StreamInfo info, final long progressMillis) {
return Completable.fromAction(() -> database.runInTransaction(() -> {
final long streamId = streamTable.upsert(new StreamEntity(info));
final var state = new StreamStateEntity(streamId, progressMillis);
if (state.isValid(info.getDuration())) {
streamStateTable.upsert(state);
}
})).subscribeOn(Schedulers.io());
}

public Maybe<StreamStateEntity> loadStreamState(final InfoItem info) {
return streamTable.getStream(info.getServiceId(), info.getUrl())
.flatMap(entity -> streamStateTable.getState(entity.getUid()))
.subscribeOn(Schedulers.io());
private boolean isSearchHistoryEnabled() {
return sharedPreferences.getBoolean(searchHistoryKey, false);
}

public Single<List<StreamStateEntity>> loadLocalStreamStateBatch(
final List<? extends LocalItem> items) {
return Single.fromCallable(() -> {
final List<StreamStateEntity> result = new ArrayList<>(items.size());
for (final LocalItem item : items) {
final long streamId;
if (item instanceof StreamStatisticsEntry) {
streamId = ((StreamStatisticsEntry) item).getStreamId();
} else if (item instanceof PlaylistStreamEntity) {
streamId = ((PlaylistStreamEntity) item).getStreamUid();
} else if (item instanceof PlaylistStreamEntry) {
streamId = ((PlaylistStreamEntry) item).getStreamId();
} else {
result.add(null);
continue;
}
result.add(streamStateTable.getState(streamId).blockingGet());
}
return result;
}).subscribeOn(Schedulers.io());
private boolean isStreamHistoryEnabled() {
return sharedPreferences.getBoolean(streamHistoryKey, false);
}

///////////////////////////////////////////////////////
// Utility
///////////////////////////////////////////////////////

public Single<Integer> removeOrphanedRecords() {
return Single.fromCallable(streamTable::deleteOrphans).subscribeOn(Schedulers.io());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ public static void initPlaylistControlClickListener(
});

// long click listener
playlistControlBinding.playlistCtrlPlayAllButton.setOnLongClickListener(view -> {
NavigationHelper.enqueueOnPlayer(activity, fragment.getPlayQueue(), PlayerType.MAIN);
return true;
});
playlistControlBinding.playlistCtrlPlayPopupButton.setOnLongClickListener(view -> {
NavigationHelper.enqueueOnPlayer(activity, fragment.getPlayQueue(), PlayerType.POPUP);
return true;
Expand Down
7 changes: 5 additions & 2 deletions app/src/main/res/layout/player.xml
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:layout_marginRight="8dp"
android:layout_marginEnd="8dp"
android:layout_weight="1"
android:gravity="top"
android:orientation="vertical"
Expand Down Expand Up @@ -160,12 +160,15 @@

<org.schabi.newpipe.views.NewPipeTextView
android:id="@+id/audioTrackTextView"
android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="35dp"
android:layout_marginEnd="8dp"
android:layout_weight="1"
android:background="?attr/selectableItemBackground"
android:gravity="center"
android:minWidth="0dp"
android:singleLine="true"
android:ellipsize="end"
android:padding="@dimen/player_main_buttons_padding"
android:textColor="@android:color/white"
android:textStyle="bold"
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -899,4 +899,7 @@
<string name="youtube_player_http_403">HTTP error 403 received from server while playing, likely caused by an IP ban or streaming URL deobfuscation issues</string>
<string name="sign_in_confirm_not_bot_error">%1$s refused to provide data, asking for a login to confirm the requester is not a bot.\n\nYour IP might have been temporarily banned by %1$s, you can wait some time or switch to a different IP (for example by turning on/off a VPN, or by switching from WiFi to mobile data).</string>
<string name="unsupported_content_in_country">This content is not available for the currently selected content country.\n\nChange your selection from \"Settings > Content > Default content country\".</string>
<string name="pref_infinite_search_history_title">Enable Infinite Search History</string>
<string name="pref_infinite_search_history_summary">Allow search history to load infinitely instead of being limited to 25 entries.</string>
<string name="infinite_search_history_key">infinite_search_history</string>
</resources>
10 changes: 10 additions & 0 deletions app/src/main/res/xml/history_settings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@
app:singleLineTitle="false"
app:iconSpaceReserved="false" />


<SwitchPreferenceCompat
android:defaultValue="false"
android:key="@string/infinite_search_history_key"
android:title="@string/pref_infinite_search_history_title"
android:summary="@string/pref_infinite_search_history_summary"
app:singleLineTitle="false"
app:iconSpaceReserved="false" />


<PreferenceCategory
android:layout="@layout/settings_category_header_layout"
android:title="@string/settings_category_clear_data_title"
Expand Down
12 changes: 6 additions & 6 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ include ':app'
// Use a local copy of NewPipe Extractor by uncommenting the lines below.
// We assume, that NewPipe and NewPipe Extractor have the same parent directory.
// If this is not the case, please change the path in includeBuild().

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please revert this change

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your review! Yes, this is part of my ANU OSS assignment. I’ll make the fixes tonight or tomorrow and update the PR soon.

//includeBuild('../NewPipeExtractor') {
// dependencySubstitution {
// substitute module('com.github.TeamNewPipe:NewPipeExtractor') using project(':extractor')
// }
//}
//
includeBuild('../NewPipeExtractor') {
dependencySubstitution {
substitute module('com.github.TeamNewPipe:NewPipeExtractor') using project(':extractor')
}
}