fix(YouTube - Spoof video streams): Do not use Android Creator for livestreams

This commit is contained in:
LisoUseInAIKyrios
2025-09-16 23:24:07 +04:00
parent 3a29f2a805
commit cbe576bc38
14 changed files with 57 additions and 44 deletions

View File

@@ -1,6 +1,4 @@
package app.revanced.extension.youtube; package app.revanced.extension.shared;
import androidx.annotation.NonNull;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@@ -39,7 +37,7 @@ public final class ByteTrieSearch extends TrieSearch<byte[]> {
return replacement; return replacement;
} }
public ByteTrieSearch(@NonNull byte[]... patterns) { public ByteTrieSearch(byte[]... patterns) {
super(new ByteTrieNode(), patterns); super(new ByteTrieNode(), patterns);
} }
} }

View File

@@ -1,6 +1,4 @@
package app.revanced.extension.youtube; package app.revanced.extension.shared;
import androidx.annotation.NonNull;
/** /**
* Text pattern searching using a prefix tree (trie). * Text pattern searching using a prefix tree (trie).
@@ -28,7 +26,7 @@ public final class StringTrieSearch extends TrieSearch<String> {
} }
} }
public StringTrieSearch(@NonNull String... patterns) { public StringTrieSearch(String... patterns) {
super(new StringTrieNode(), patterns); super(new StringTrieNode(), patterns);
} }
} }

View File

@@ -1,6 +1,5 @@
package app.revanced.extension.youtube; package app.revanced.extension.shared;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
@@ -8,8 +7,7 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
/** /**Searches for a group of different patterns using a trie (prefix tree).
* Searches for a group of different patterns using a trie (prefix tree).
* Can significantly speed up searching for multiple patterns. * Can significantly speed up searching for multiple patterns.
*/ */
public abstract class TrieSearch<T> { public abstract class TrieSearch<T> {
@@ -136,7 +134,7 @@ public abstract class TrieSearch<T> {
* @param patternLength Length of the pattern. * @param patternLength Length of the pattern.
* @param callback Callback, where a value of NULL indicates to always accept a pattern match. * @param callback Callback, where a value of NULL indicates to always accept a pattern match.
*/ */
private void addPattern(@NonNull T pattern, int patternIndex, int patternLength, private void addPattern(T pattern, int patternIndex, int patternLength,
@Nullable TriePatternMatchedCallback<T> callback) { @Nullable TriePatternMatchedCallback<T> callback) {
if (patternIndex == patternLength) { // Reached the end of the pattern. if (patternIndex == patternLength) { // Reached the end of the pattern.
if (endOfPatternCallback == null) { if (endOfPatternCallback == null) {
@@ -308,13 +306,13 @@ public abstract class TrieSearch<T> {
private final List<T> patterns = new ArrayList<>(); private final List<T> patterns = new ArrayList<>();
@SafeVarargs @SafeVarargs
TrieSearch(@NonNull TrieNode<T> root, @NonNull T... patterns) { TrieSearch(TrieNode<T> root, T... patterns) {
this.root = Objects.requireNonNull(root); this.root = Objects.requireNonNull(root);
addPatterns(patterns); addPatterns(patterns);
} }
@SafeVarargs @SafeVarargs
public final void addPatterns(@NonNull T... patterns) { public final void addPatterns(T... patterns) {
for (T pattern : patterns) { for (T pattern : patterns) {
addPattern(pattern); addPattern(pattern);
} }
@@ -325,7 +323,7 @@ public abstract class TrieSearch<T> {
* *
* @param pattern Pattern to add. Calling this with a zero length pattern does nothing. * @param pattern Pattern to add. Calling this with a zero length pattern does nothing.
*/ */
public void addPattern(@NonNull T pattern) { public void addPattern(T pattern) {
addPattern(pattern, root.getTextLength(pattern), null); addPattern(pattern, root.getTextLength(pattern), null);
} }
@@ -333,31 +331,31 @@ public abstract class TrieSearch<T> {
* @param pattern Pattern to add. Calling this with a zero length pattern does nothing. * @param pattern Pattern to add. Calling this with a zero length pattern does nothing.
* @param callback Callback to determine if searching should halt when a match is found. * @param callback Callback to determine if searching should halt when a match is found.
*/ */
public void addPattern(@NonNull T pattern, @NonNull TriePatternMatchedCallback<T> callback) { public void addPattern(T pattern, TriePatternMatchedCallback<T> callback) {
addPattern(pattern, root.getTextLength(pattern), Objects.requireNonNull(callback)); addPattern(pattern, root.getTextLength(pattern), Objects.requireNonNull(callback));
} }
void addPattern(@NonNull T pattern, int patternLength, @Nullable TriePatternMatchedCallback<T> callback) { void addPattern(T pattern, int patternLength, @Nullable TriePatternMatchedCallback<T> callback) {
if (patternLength == 0) return; // Nothing to match if (patternLength == 0) return; // Nothing to match
patterns.add(pattern); patterns.add(pattern);
root.addPattern(pattern, 0, patternLength, callback); root.addPattern(pattern, 0, patternLength, callback);
} }
public final boolean matches(@NonNull T textToSearch) { public final boolean matches(T textToSearch) {
return matches(textToSearch, 0); return matches(textToSearch, 0);
} }
public boolean matches(@NonNull T textToSearch, @NonNull Object callbackParameter) { public boolean matches(T textToSearch, Object callbackParameter) {
return matches(textToSearch, 0, root.getTextLength(textToSearch), return matches(textToSearch, 0, root.getTextLength(textToSearch),
Objects.requireNonNull(callbackParameter)); Objects.requireNonNull(callbackParameter));
} }
public boolean matches(@NonNull T textToSearch, int startIndex) { public boolean matches(T textToSearch, int startIndex) {
return matches(textToSearch, startIndex, root.getTextLength(textToSearch)); return matches(textToSearch, startIndex, root.getTextLength(textToSearch));
} }
public final boolean matches(@NonNull T textToSearch, int startIndex, int endIndex) { public final boolean matches(T textToSearch, int startIndex, int endIndex) {
return matches(textToSearch, startIndex, endIndex, null); return matches(textToSearch, startIndex, endIndex, null);
} }
@@ -370,11 +368,11 @@ public abstract class TrieSearch<T> {
* @param callbackParameter Optional parameter passed to the callbacks. * @param callbackParameter Optional parameter passed to the callbacks.
* @return If any pattern matched, and it's callback halted searching. * @return If any pattern matched, and it's callback halted searching.
*/ */
public boolean matches(@NonNull T textToSearch, int startIndex, int endIndex, @Nullable Object callbackParameter) { public boolean matches(T textToSearch, int startIndex, int endIndex, @Nullable Object callbackParameter) {
return matches(textToSearch, root.getTextLength(textToSearch), startIndex, endIndex, callbackParameter); return matches(textToSearch, root.getTextLength(textToSearch), startIndex, endIndex, callbackParameter);
} }
private boolean matches(@NonNull T textToSearch, int textToSearchLength, int startIndex, int endIndex, private boolean matches(T textToSearch, int textToSearchLength, int startIndex, int endIndex,
@Nullable Object callbackParameter) { @Nullable Object callbackParameter) {
if (endIndex > textToSearchLength) { if (endIndex > textToSearchLength) {
throw new IllegalArgumentException("endIndex: " + endIndex throw new IllegalArgumentException("endIndex: " + endIndex

View File

@@ -1,5 +1,6 @@
package app.revanced.extension.shared.spoof.requests; package app.revanced.extension.shared.spoof.requests;
import static app.revanced.extension.shared.ByteTrieSearch.convertStringsToBytes;
import static app.revanced.extension.shared.spoof.requests.PlayerRoutes.GET_STREAMING_DATA; import static app.revanced.extension.shared.spoof.requests.PlayerRoutes.GET_STREAMING_DATA;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@@ -13,12 +14,18 @@ import java.net.HttpURLConnection;
import java.net.SocketTimeoutException; import java.net.SocketTimeoutException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.*; import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import app.revanced.extension.shared.ByteTrieSearch;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.BaseSettings; import app.revanced.extension.shared.settings.BaseSettings;
@@ -93,6 +100,16 @@ public class StreamingDataRequest {
} }
}); });
/**
* Strings found in the response if the video is a livestream.
*/
private static final ByteTrieSearch liveStreamBufferSearch = new ByteTrieSearch(
convertStringsToBytes(
"yt_live_broadcast",
"yt_premiere_broadcast"
)
);
private static volatile ClientType lastSpoofedClientType; private static volatile ClientType lastSpoofedClientType;
public static String getLastSpoofedClientName() { public static String getLastSpoofedClientName() {
@@ -221,9 +238,13 @@ public class StreamingDataRequest {
while ((bytesRead = inputStream.read(buffer)) >= 0) { while ((bytesRead = inputStream.read(buffer)) >= 0) {
baos.write(buffer, 0, bytesRead); baos.write(buffer, 0, bytesRead);
} }
lastSpoofedClientType = clientType; if (clientType == ClientType.ANDROID_CREATOR && liveStreamBufferSearch.matches(buffer)) {
Logger.printDebug(() -> "Skipping Android Studio as video is a livestream: " + videoId);
} else {
lastSpoofedClientType = clientType;
return ByteBuffer.wrap(baos.toByteArray()); return ByteBuffer.wrap(baos.toByteArray());
}
} }
} }
} catch (IOException ex) { } catch (IOException ex) {

View File

@@ -10,7 +10,7 @@ import java.util.List;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.youtube.StringTrieSearch; import app.revanced.extension.shared.StringTrieSearch;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused") @SuppressWarnings("unused")

View File

@@ -14,7 +14,7 @@ import java.util.regex.Pattern;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.youtube.ByteTrieSearch; import app.revanced.extension.shared.ByteTrieSearch;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
/** /**

View File

@@ -1,6 +1,6 @@
package app.revanced.extension.youtube.patches.components; package app.revanced.extension.youtube.patches.components;
import app.revanced.extension.youtube.StringTrieSearch; import app.revanced.extension.shared.StringTrieSearch;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.shared.PlayerType; import app.revanced.extension.youtube.shared.PlayerType;

View File

@@ -4,7 +4,7 @@ import androidx.annotation.NonNull;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.settings.BooleanSetting; import app.revanced.extension.shared.settings.BooleanSetting;
import app.revanced.extension.youtube.ByteTrieSearch; import app.revanced.extension.shared.ByteTrieSearch;
abstract class FilterGroup<T> { abstract class FilterGroup<T> {
final static class FilterGroupResult { final static class FilterGroupResult {

View File

@@ -5,9 +5,9 @@ import androidx.annotation.NonNull;
import java.util.*; import java.util.*;
import java.util.function.Consumer; import java.util.function.Consumer;
import app.revanced.extension.youtube.ByteTrieSearch; import app.revanced.extension.shared.ByteTrieSearch;
import app.revanced.extension.youtube.StringTrieSearch; import app.revanced.extension.shared.StringTrieSearch;
import app.revanced.extension.youtube.TrieSearch; import app.revanced.extension.shared.TrieSearch;
abstract class FilterGroupList<V, T extends FilterGroup<V>> implements Iterable<T> { abstract class FilterGroupList<V, T extends FilterGroup<V>> implements Iterable<T> {

View File

@@ -14,9 +14,9 @@ import java.util.concurrent.atomic.AtomicReference;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.youtube.ByteTrieSearch; import app.revanced.extension.shared.ByteTrieSearch;
import app.revanced.extension.youtube.StringTrieSearch; import app.revanced.extension.shared.StringTrieSearch;
import app.revanced.extension.youtube.TrieSearch; import app.revanced.extension.shared.TrieSearch;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.shared.NavigationBar; import app.revanced.extension.youtube.shared.NavigationBar;
import app.revanced.extension.youtube.shared.PlayerType; import app.revanced.extension.youtube.shared.PlayerType;

View File

@@ -10,7 +10,7 @@ import androidx.annotation.Nullable;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.youtube.StringTrieSearch; import app.revanced.extension.shared.StringTrieSearch;
import app.revanced.extension.youtube.patches.ChangeHeaderPatch; import app.revanced.extension.youtube.patches.ChangeHeaderPatch;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.shared.NavigationBar; import app.revanced.extension.youtube.shared.NavigationBar;

View File

@@ -8,7 +8,7 @@ import java.util.List;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.settings.BaseSettings; import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.youtube.StringTrieSearch; import app.revanced.extension.shared.StringTrieSearch;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused") @SuppressWarnings("unused")

View File

@@ -12,7 +12,7 @@ import app.revanced.extension.youtube.patches.ReturnYouTubeDislikePatch;
import app.revanced.extension.youtube.patches.VideoInformation; import app.revanced.extension.youtube.patches.VideoInformation;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.youtube.TrieSearch; import app.revanced.extension.shared.TrieSearch;
/** /**
* Searches for video id's in the proto buffer of Shorts dislike. * Searches for video id's in the proto buffer of Shorts dislike.

View File

@@ -22,11 +22,9 @@ public class SpoofVideoStreamsPatch {
List<ClientType> availableClients = List.of( List<ClientType> availableClients = List.of(
ANDROID_VR_1_61_48, ANDROID_VR_1_61_48,
VISIONOS, VISIONOS,
IPADOS,
// Creator must be next to last, because livestreams fetch successfully but don't playback.
ANDROID_CREATOR, ANDROID_CREATOR,
// VR 1.43 must be last as spoof streaming data handles it slightly differently. ANDROID_VR_1_43_32,
ANDROID_VR_1_43_32 IPADOS
); );
StreamingDataRequest.setClientOrderToUse(availableClients, StreamingDataRequest.setClientOrderToUse(availableClients,