Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf: Use KMP to search Shorts video id #109

Merged
merged 24 commits into from
Jan 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
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
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import androidx.annotation.Nullable;

import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;

import app.revanced.extension.shared.patches.components.ByteArrayFilterGroup;
Expand Down Expand Up @@ -36,11 +35,12 @@
public final class ReturnYouTubeDislikeFilterPatch extends Filter {

/**
* Last unique video id's loaded. Value is ignored and Map is treated as a Set.
* Cannot use {@link LinkedHashSet} because it's missing #removeEldestEntry().
* Last unique video id's loaded.
* Key is a String represeting the video id.
* Value is a ByteArrayFilterGroup used for performing KMP pattern searching.
*/
@GuardedBy("itself")
private static final Map<String, Boolean> lastVideoIds = new LinkedHashMap<>() {
private static final Map<String, ByteArrayFilterGroup> lastVideoIds = new LinkedHashMap<>() {
/**
* Number of video id's to keep track of for searching thru the buffer.
* A minimum value of 3 should be sufficient, but check a few more just in case.
Expand Down Expand Up @@ -101,8 +101,11 @@ public static void newPlayerResponseVideoId(String videoId, boolean isShortAndOp
return;
}
synchronized (lastVideoIds) {
if (lastVideoIds.put(videoId, Boolean.TRUE) == null) {
Logger.printDebug(() -> "New Short video id: " + videoId);
if (!lastVideoIds.containsKey(videoId)) {
Logger.printDebug(() -> "New Shorts video id: " + videoId);
// Put a placeholder first
lastVideoIds.put(videoId, null);
lastVideoIds.put(videoId, new ByteArrayFilterGroup(null, videoId));
}
}
} catch (Exception ex) {
Expand All @@ -114,7 +117,12 @@ public static void newPlayerResponseVideoId(String videoId, boolean isShortAndOp
* This could use {@link TrieSearch}, but since the patterns are constantly changing
* the overhead of updating the Trie might negate the search performance gain.
*/
private static boolean byteArrayContainsString(@NonNull byte[] array, @NonNull String text) {
private static boolean byteArrayContainsString(@NonNull byte[] array, @NonNull String text,
@Nullable ByteArrayFilterGroup videoIdFilter) {
// If a video filter is available, check it first.
if (videoIdFilter != null) {
return videoIdFilter.check(array).isFiltered();
}
for (int i = 0, lastArrayStartIndex = array.length - text.length(); i <= lastArrayStartIndex; i++) {
boolean found = true;
for (int j = 0, textLength = text.length(); j < textLength; j++) {
Expand Down Expand Up @@ -154,12 +162,14 @@ public boolean isFiltered(String path, @Nullable String identifier, String allVa
@Nullable
private String findVideoId(byte[] protobufBufferArray) {
synchronized (lastVideoIds) {
for (String videoId : lastVideoIds.keySet()) {
if (byteArrayContainsString(protobufBufferArray, videoId)) {
for (Map.Entry<String, ByteArrayFilterGroup> entry : lastVideoIds.entrySet()) {
final String videoId = entry.getKey();
final ByteArrayFilterGroup videoIdFilter = entry.getValue();
if (byteArrayContainsString(protobufBufferArray, videoId, videoIdFilter)) {
return videoId;
}
}

return null;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import androidx.annotation.Nullable;

import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;

import app.revanced.extension.shared.patches.components.ByteArrayFilterGroup;
Expand All @@ -27,11 +26,12 @@ public final class ShortsCustomActionsFilter extends Filter {
Settings.ENABLE_SHORTS_CUSTOM_ACTIONS_FLYOUT_MENU.get();

/**
* Last unique video id's loaded. Value is ignored and Map is treated as a Set.
* Cannot use {@link LinkedHashSet} because it's missing #removeEldestEntry().
* Last unique video id's loaded.
* Key is a String represeting the video id.
* Value is a ByteArrayFilterGroup used for performing KMP pattern searching.
*/
@GuardedBy("itself")
private static final Map<String, Boolean> lastVideoIds = new LinkedHashMap<>() {
private static final Map<String, ByteArrayFilterGroup> lastVideoIds = new LinkedHashMap<>() {
/**
* Number of video id's to keep track of for searching thru the buffer.
* A minimum value of 3 should be sufficient, but check a few more just in case.
Expand Down Expand Up @@ -114,7 +114,11 @@ public static void newPlayerResponseVideoId(String videoId, boolean isShortAndOp
return;
}
synchronized (lastVideoIds) {
lastVideoIds.putIfAbsent(videoId, Boolean.TRUE);
if (!lastVideoIds.containsKey(videoId)) {
// Put a placeholder first
lastVideoIds.put(videoId, null);
lastVideoIds.put(videoId, new ByteArrayFilterGroup(null, videoId));
}
}
} catch (Exception ex) {
Logger.printException(() -> "newPlayerResponseVideoId failure", ex);
Expand All @@ -126,7 +130,12 @@ public static void newPlayerResponseVideoId(String videoId, boolean isShortAndOp
* This could use {@link TrieSearch}, but since the patterns are constantly changing
* the overhead of updating the Trie might negate the search performance gain.
*/
private static boolean byteArrayContainsString(@NonNull byte[] array, @NonNull String text) {
private static boolean byteArrayContainsString(@NonNull byte[] array, @NonNull String text,
@Nullable ByteArrayFilterGroup videoIdFilter) {
// If a video filter is available, check it first.
if (videoIdFilter != null) {
return videoIdFilter.check(array).isFiltered();
}
for (int i = 0, lastArrayStartIndex = array.length - text.length(); i <= lastArrayStartIndex; i++) {
boolean found = true;
for (int j = 0, textLength = text.length(); j < textLength; j++) {
Expand Down Expand Up @@ -161,9 +170,12 @@ public boolean isFiltered(String path, @Nullable String identifier, String allVa

private void findVideoId(byte[] protobufBufferArray) {
synchronized (lastVideoIds) {
for (String videoId : lastVideoIds.keySet()) {
if (byteArrayContainsString(protobufBufferArray, videoId)) {
for (Map.Entry<String, ByteArrayFilterGroup> entry : lastVideoIds.entrySet()) {
final String videoId = entry.getKey();
final ByteArrayFilterGroup videoIdFilter = entry.getValue();
if (byteArrayContainsString(protobufBufferArray, videoId, videoIdFilter)) {
setShortsVideoId(videoId, false);
return;
}
}
}
Expand Down