perf: Use KMP to search Shorts video id (#109)

* ci: workflow to ping Discord users when patches are released (#72)

* init: Workflow to notify discord users of releases

* Rename workflow

* chore (Background playback): Shorten description

* Revert "chore (Background playback): Shorten description"

This reverts commit 10661b870f0c9c670c5d522f9b2ca7cc82d32772.

* Change message contents

* perf: Use KMP to search Shorts videoId

* Beautify

* Incorrect key-value

* Incorrect key-value

* Rename variable

* Comment

* Comment

* Comment

* Comment

* Beauty

* fix: video id is not updated in some case

* fix build error

* chore: Simplify

---------

Co-authored-by: KobeW50 <84587632+KobeW50@users.noreply.github.com>
Co-authored-by: inotia00 <108592928+inotia00@users.noreply.github.com>
This commit is contained in:
Hoàng Gia Bảo 2025-01-20 17:06:48 +07:00 committed by GitHub
parent 6bb067f6ec
commit a6f8596a0d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 40 additions and 18 deletions

View File

@ -5,7 +5,6 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map; import java.util.Map;
import app.revanced.extension.shared.patches.components.ByteArrayFilterGroup; import app.revanced.extension.shared.patches.components.ByteArrayFilterGroup;
@ -36,11 +35,12 @@ import app.revanced.extension.youtube.shared.VideoInformation;
public final class ReturnYouTubeDislikeFilterPatch extends Filter { public final class ReturnYouTubeDislikeFilterPatch extends Filter {
/** /**
* Last unique video id's loaded. Value is ignored and Map is treated as a Set. * Last unique video id's loaded.
* Cannot use {@link LinkedHashSet} because it's missing #removeEldestEntry(). * Key is a String represeting the video id.
* Value is a ByteArrayFilterGroup used for performing KMP pattern searching.
*/ */
@GuardedBy("itself") @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. * 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. * A minimum value of 3 should be sufficient, but check a few more just in case.
@ -101,8 +101,11 @@ public final class ReturnYouTubeDislikeFilterPatch extends Filter {
return; return;
} }
synchronized (lastVideoIds) { synchronized (lastVideoIds) {
if (lastVideoIds.put(videoId, Boolean.TRUE) == null) { if (!lastVideoIds.containsKey(videoId)) {
Logger.printDebug(() -> "New Short video id: " + 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) { } catch (Exception ex) {
@ -114,7 +117,12 @@ public final class ReturnYouTubeDislikeFilterPatch extends Filter {
* This could use {@link TrieSearch}, but since the patterns are constantly changing * This could use {@link TrieSearch}, but since the patterns are constantly changing
* the overhead of updating the Trie might negate the search performance gain. * 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++) { for (int i = 0, lastArrayStartIndex = array.length - text.length(); i <= lastArrayStartIndex; i++) {
boolean found = true; boolean found = true;
for (int j = 0, textLength = text.length(); j < textLength; j++) { for (int j = 0, textLength = text.length(); j < textLength; j++) {
@ -154,8 +162,10 @@ public final class ReturnYouTubeDislikeFilterPatch extends Filter {
@Nullable @Nullable
private String findVideoId(byte[] protobufBufferArray) { private String findVideoId(byte[] protobufBufferArray) {
synchronized (lastVideoIds) { synchronized (lastVideoIds) {
for (String videoId : lastVideoIds.keySet()) { for (Map.Entry<String, ByteArrayFilterGroup> entry : lastVideoIds.entrySet()) {
if (byteArrayContainsString(protobufBufferArray, videoId)) { final String videoId = entry.getKey();
final ByteArrayFilterGroup videoIdFilter = entry.getValue();
if (byteArrayContainsString(protobufBufferArray, videoId, videoIdFilter)) {
return videoId; return videoId;
} }
} }

View File

@ -7,7 +7,6 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map; import java.util.Map;
import app.revanced.extension.shared.patches.components.ByteArrayFilterGroup; import app.revanced.extension.shared.patches.components.ByteArrayFilterGroup;
@ -30,11 +29,12 @@ public final class ShortsCustomActionsFilter extends Filter {
SHORTS_CUSTOM_ACTIONS_FLYOUT_MENU_ENABLED || SHORTS_CUSTOM_ACTIONS_TOOLBAR_ENABLED; SHORTS_CUSTOM_ACTIONS_FLYOUT_MENU_ENABLED || SHORTS_CUSTOM_ACTIONS_TOOLBAR_ENABLED;
/** /**
* Last unique video id's loaded. Value is ignored and Map is treated as a Set. * Last unique video id's loaded.
* Cannot use {@link LinkedHashSet} because it's missing #removeEldestEntry(). * Key is a String represeting the video id.
* Value is a ByteArrayFilterGroup used for performing KMP pattern searching.
*/ */
@GuardedBy("itself") @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. * 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. * A minimum value of 3 should be sufficient, but check a few more just in case.
@ -117,7 +117,11 @@ public final class ShortsCustomActionsFilter extends Filter {
return; return;
} }
synchronized (lastVideoIds) { 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) { } catch (Exception ex) {
Logger.printException(() -> "newPlayerResponseVideoId failure", ex); Logger.printException(() -> "newPlayerResponseVideoId failure", ex);
@ -129,7 +133,12 @@ public final class ShortsCustomActionsFilter extends Filter {
* This could use {@link TrieSearch}, but since the patterns are constantly changing * This could use {@link TrieSearch}, but since the patterns are constantly changing
* the overhead of updating the Trie might negate the search performance gain. * 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++) { for (int i = 0, lastArrayStartIndex = array.length - text.length(); i <= lastArrayStartIndex; i++) {
boolean found = true; boolean found = true;
for (int j = 0, textLength = text.length(); j < textLength; j++) { for (int j = 0, textLength = text.length(); j < textLength; j++) {
@ -164,9 +173,12 @@ public final class ShortsCustomActionsFilter extends Filter {
private void findVideoId(byte[] protobufBufferArray) { private void findVideoId(byte[] protobufBufferArray) {
synchronized (lastVideoIds) { synchronized (lastVideoIds) {
for (String videoId : lastVideoIds.keySet()) { for (Map.Entry<String, ByteArrayFilterGroup> entry : lastVideoIds.entrySet()) {
if (byteArrayContainsString(protobufBufferArray, videoId)) { final String videoId = entry.getKey();
final ByteArrayFilterGroup videoIdFilter = entry.getValue();
if (byteArrayContainsString(protobufBufferArray, videoId, videoIdFilter)) {
setShortsVideoId(videoId, false); setShortsVideoId(videoId, false);
return;
} }
} }
} }