add relocated and shaded protobuf to handle protobuf
This commit is contained in:
parent
025d6daa35
commit
5d80ec9a53
18 changed files with 238 additions and 79 deletions
|
|
@ -1,5 +1,5 @@
|
|||
plugins {
|
||||
alias(libs.plugins.android.library)
|
||||
alias(libs.plugins.android.library)
|
||||
}
|
||||
|
||||
android {
|
||||
|
|
@ -19,4 +19,6 @@ android {
|
|||
dependencies {
|
||||
compileOnly(libs.annotation)
|
||||
compileOnly(libs.okhttp)
|
||||
compileOnly(libs.protobuf.javalite)
|
||||
implementation(project(":extensions:shared:protobuf", configuration = "shadowRuntimeElements"))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ public class BaseSettings {
|
|||
//
|
||||
|
||||
public static final BooleanSetting SPOOF_VIDEO_STREAMS = new BooleanSetting("revanced_spoof_video_streams", TRUE, true, "revanced_spoof_video_streams_user_dialog_message");
|
||||
public static final BooleanSetting SPOOF_STREAMING_DATA_STATS_FOR_NERDS = new BooleanSetting("revanced_spoof_streaming_data_stats_for_nerds", TRUE, parent(SPOOF_VIDEO_STREAMS));
|
||||
public static final BooleanSetting SPOOF_STREAMING_DATA_STATS_FOR_NERDS = new BooleanSetting("revanced_spoof_video_streams_stats_for_nerds", TRUE, parent(SPOOF_VIDEO_STREAMS));
|
||||
|
||||
public static final BooleanSetting SANITIZE_SHARING_LINKS = new BooleanSetting("revanced_sanitize_sharing_links", TRUE);
|
||||
public static final BooleanSetting REPLACE_MUSIC_LINKS_WITH_YOUTUBE = new BooleanSetting("revanced_replace_music_with_youtube", FALSE);
|
||||
|
|
|
|||
|
|
@ -28,12 +28,11 @@ public enum ClientType {
|
|||
Build.VERSION.RELEASE,
|
||||
String.valueOf(Build.VERSION.SDK_INT),
|
||||
Build.ID,
|
||||
"20.26.46",
|
||||
"20.44.38",
|
||||
// This client has been used by most open-source YouTube stream extraction tools since 2024, including NewPipe Extractor, SmartTube, and Grayjay.
|
||||
// This client can log in, but if an access token is used in the request, GVS can more easily identify the request as coming from ReVanced.
|
||||
// This means that the GVS server can strengthen its validation of the ANDROID_REEL client.
|
||||
// For this reason, ANDROID_REEL is used as a logout client.
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
"Android Reel"
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import android.text.TextUtils;
|
|||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
|
@ -39,7 +38,7 @@ public class SpoofVideoStreamsPatch {
|
|||
@Nullable
|
||||
private static volatile AppLanguage languageOverride;
|
||||
|
||||
private static volatile ClientType preferredClient = ClientType.ANDROID_VR_1_43_32;
|
||||
private static volatile ClientType preferredClient = ClientType.ANDROID_REEL;
|
||||
|
||||
/**
|
||||
* @return If this patch was included during patching.
|
||||
|
|
@ -250,7 +249,7 @@ public class SpoofVideoStreamsPatch {
|
|||
* Called after {@link #fetchStreams(String, Map)}.
|
||||
*/
|
||||
@Nullable
|
||||
public static ByteBuffer getStreamingData(String videoId) {
|
||||
public static byte[] getStreamingData(String videoId) {
|
||||
if (SPOOF_STREAMING_DATA) {
|
||||
try {
|
||||
StreamingDataRequest request = StreamingDataRequest.getRequestForVideoId(videoId);
|
||||
|
|
|
|||
|
|
@ -1,19 +1,17 @@
|
|||
package app.revanced.extension.shared.spoof.requests;
|
||||
|
||||
import static app.revanced.extension.shared.ByteTrieSearch.convertStringsToBytes;
|
||||
import static app.revanced.extension.shared.Utils.isNotEmpty;
|
||||
import static app.revanced.extension.shared.spoof.requests.PlayerRoutes.GET_PLAYER_STREAMING_DATA;
|
||||
import static app.revanced.extension.shared.spoof.requests.PlayerRoutes.GET_REEL_STREAMING_DATA;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
|
@ -28,6 +26,10 @@ import java.util.concurrent.TimeoutException;
|
|||
import app.revanced.extension.shared.ByteTrieSearch;
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.innertube.PlayerResponseOuterClass;
|
||||
import app.revanced.extension.shared.innertube.PlayerResponseOuterClass.PlayerResponse;
|
||||
import app.revanced.extension.shared.innertube.PlayerResponseOuterClass.StreamingData;
|
||||
import app.revanced.extension.shared.innertube.ReelItemWatchResponseOuterClass.ReelItemWatchResponse;
|
||||
import app.revanced.extension.shared.requests.Route;
|
||||
import app.revanced.extension.shared.settings.BaseSettings;
|
||||
import app.revanced.extension.shared.spoof.ClientType;
|
||||
|
|
@ -43,7 +45,7 @@ import app.revanced.extension.shared.spoof.ClientType;
|
|||
*/
|
||||
public class StreamingDataRequest {
|
||||
|
||||
private static volatile ClientType[] clientOrderToUse = ClientType.values();
|
||||
private static volatile ClientType[] clientOrderToUse = ClientType.values();
|
||||
|
||||
public static void setClientOrderToUse(List<ClientType> availableClients, ClientType preferredClient) {
|
||||
Objects.requireNonNull(preferredClient);
|
||||
|
|
@ -113,7 +115,7 @@ public class StreamingDataRequest {
|
|||
|
||||
private final String videoId;
|
||||
|
||||
private final Future<ByteBuffer> future;
|
||||
private final Future<byte[]> future;
|
||||
|
||||
private StreamingDataRequest(String videoId, Map<String, String> playerHeaders) {
|
||||
Objects.requireNonNull(playerHeaders);
|
||||
|
|
@ -136,6 +138,12 @@ public class StreamingDataRequest {
|
|||
Logger.printInfo(() -> toastMessage, ex);
|
||||
}
|
||||
|
||||
private static void handleDebugToast(String toastMessage, ClientType clientType) {
|
||||
if (BaseSettings.DEBUG.get() && BaseSettings.DEBUG_TOAST_ON_ERROR.get()) {
|
||||
Utils.showToastShort(String.format(toastMessage, clientType));
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static HttpURLConnection send(ClientType clientType,
|
||||
String videoId,
|
||||
|
|
@ -208,7 +216,7 @@ public class StreamingDataRequest {
|
|||
return null;
|
||||
}
|
||||
|
||||
private static ByteBuffer fetch(String videoId, Map<String, String> playerHeaders) {
|
||||
private static byte[] fetch(String videoId, Map<String, String> playerHeaders) {
|
||||
final boolean debugEnabled = BaseSettings.DEBUG.get();
|
||||
|
||||
// Retry with different client if empty response body is received.
|
||||
|
|
@ -219,33 +227,11 @@ public class StreamingDataRequest {
|
|||
|
||||
HttpURLConnection connection = send(clientType, videoId, playerHeaders, showErrorToast);
|
||||
if (connection != null) {
|
||||
try {
|
||||
// gzip encoding doesn't response with content length (-1),
|
||||
// but empty response body does.
|
||||
if (connection.getContentLength() == 0) {
|
||||
if (BaseSettings.DEBUG.get() && BaseSettings.DEBUG_TOAST_ON_ERROR.get()) {
|
||||
Utils.showToastShort("Debug: Ignoring empty spoof stream client " + clientType);
|
||||
}
|
||||
} else {
|
||||
try (InputStream inputStream = new BufferedInputStream(connection.getInputStream());
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
|
||||
byte[] playerResponseBuffer = buildPlayerResponseBuffer(clientType, connection);
|
||||
if (playerResponseBuffer != null) {
|
||||
lastSpoofedClientType = clientType;
|
||||
|
||||
byte[] buffer = new byte[2048];
|
||||
int bytesRead;
|
||||
while ((bytesRead = inputStream.read(buffer)) >= 0) {
|
||||
baos.write(buffer, 0, bytesRead);
|
||||
}
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
Logger.printException(() -> "Fetch failed while processing response data", ex);
|
||||
return playerResponseBuffer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -255,12 +241,61 @@ public class StreamingDataRequest {
|
|||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static byte[] buildPlayerResponseBuffer(ClientType clientType,
|
||||
HttpURLConnection connection) {
|
||||
// gzip encoding doesn't response with content length (-1),
|
||||
// but empty response body does.
|
||||
if (connection.getContentLength() == 0) {
|
||||
handleDebugToast("Debug: Ignoring empty spoof stream client (%s)", clientType);
|
||||
return null;
|
||||
}
|
||||
|
||||
try (InputStream inputStream = connection.getInputStream()) {
|
||||
PlayerResponse playerResponse = clientType.usePlayerEndpoint
|
||||
? PlayerResponse.parseFrom(inputStream)
|
||||
: ReelItemWatchResponse.parseFrom(inputStream).getPlayerResponse();
|
||||
|
||||
var playabilityStatus = playerResponse.getPlayabilityStatus();
|
||||
if (playabilityStatus.getStatus() != PlayerResponseOuterClass.Status.OK) {
|
||||
handleDebugToast("Debug: Ignoring unplayable video (%s)", clientType);
|
||||
String reason = playabilityStatus.getReason();
|
||||
if (isNotEmpty(reason)) {
|
||||
Logger.printDebug(() -> String.format("Debug: Ignoring unplayable video (%s), reason: %s", clientType, reason));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
PlayerResponse.Builder responseBuilder = playerResponse.toBuilder();
|
||||
if (!playerResponse.hasStreamingData()) {
|
||||
handleDebugToast("Debug: Ignoring empty streaming data (%s)", clientType);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Android Studio only supports the HLS protocol for live streams.
|
||||
// HLS protocol can theoretically be played with ExoPlayer,
|
||||
// but the related code has not yet been implemented.
|
||||
// If DASH protocol is not available, the client will be skipped.
|
||||
StreamingData streamingData = playerResponse.getStreamingData();
|
||||
if (streamingData.getAdaptiveFormatsCount() == 0) {
|
||||
handleDebugToast("Debug: Ignoring empty adaptiveFormat (%s)", clientType);
|
||||
return null;
|
||||
}
|
||||
|
||||
return responseBuilder.build().toByteArray();
|
||||
} catch (IOException ex) {
|
||||
Logger.printException(() -> "Failed to write player response to buffer array", ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean fetchCompleted() {
|
||||
return future.isDone();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ByteBuffer getStream() {
|
||||
public byte[] getStream() {
|
||||
try {
|
||||
return future.get(MAX_MILLISECONDS_TO_WAIT_FOR_FETCH, TimeUnit.MILLISECONDS);
|
||||
} catch (TimeoutException ex) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue