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
|
|
@ -18,8 +18,8 @@ public class SpoofVideoStreamsPatch {
|
|||
*/
|
||||
public static void setClientOrderToUse() {
|
||||
List<ClientType> availableClients = List.of(
|
||||
ANDROID_VR_1_43_32,
|
||||
ANDROID_REEL,
|
||||
ANDROID_VR_1_43_32,
|
||||
VISIONOS,
|
||||
ANDROID_VR_1_61_48
|
||||
);
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ public class Settings extends YouTubeAndMusicSettings {
|
|||
|
||||
// Miscellaneous
|
||||
public static final EnumSetting<ClientType> SPOOF_VIDEO_STREAMS_CLIENT_TYPE = new EnumSetting<>("revanced_spoof_video_streams_client_type",
|
||||
ClientType.ANDROID_VR_1_43_32, true, parent(SPOOF_VIDEO_STREAMS));
|
||||
ClientType.ANDROID_REEL, true, parent(SPOOF_VIDEO_STREAMS));
|
||||
|
||||
public static final BooleanSetting FORCE_ORIGINAL_AUDIO = new BooleanSetting("revanced_force_original_audio", TRUE, true);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
55
extensions/shared/protobuf/build.gradle.kts
Normal file
55
extensions/shared/protobuf/build.gradle.kts
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
||||
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
alias(libs.plugins.protobuf)
|
||||
alias(libs.plugins.shadow)
|
||||
}
|
||||
|
||||
val shade: Configuration by configurations.creating {
|
||||
configurations.getByName("compileClasspath").extendsFrom(this)
|
||||
configurations.getByName("runtimeClasspath").extendsFrom(this)
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly(libs.annotation)
|
||||
compileOnly(libs.okhttp)
|
||||
shade(libs.protobuf.javalite)
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
// Make sure generated proto sources are compiled and end up in the shaded jar
|
||||
main {
|
||||
java.srcDir("$buildDir/generated/source/proto/main/java")
|
||||
}
|
||||
}
|
||||
|
||||
protobuf {
|
||||
protoc {
|
||||
artifact = libs.protobuf.protoc.get().toString()
|
||||
}
|
||||
|
||||
generateProtoTasks {
|
||||
all().forEach { task ->
|
||||
task.builtins {
|
||||
named("java") {
|
||||
option("lite")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val shadowJar = tasks.named<ShadowJar>("shadowJar") {
|
||||
configurations = listOf(shade)
|
||||
relocate("com.google.protobuf", "app.revanced.com.google.protobuf")
|
||||
}
|
||||
|
||||
configurations.named("runtimeElements") {
|
||||
isCanBeConsumed = true
|
||||
isCanBeResolved = false
|
||||
|
||||
outgoing.artifacts.clear()
|
||||
outgoing.artifact(shadowJar)
|
||||
}!!.let { artifacts { add(it.name, shadowJar) } }
|
||||
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package app.revanced.extension.shared.innertube;
|
||||
|
||||
option optimize_for = LITE_RUNTIME;
|
||||
option java_package = "app.revanced.extension.shared.innertube";
|
||||
|
||||
message PlayerResponse {
|
||||
oneof data {
|
||||
PlayabilityStatus playability_status = 2;
|
||||
StreamingData streaming_data = 4;
|
||||
}
|
||||
}
|
||||
|
||||
message PlayabilityStatus {
|
||||
Status status = 1;
|
||||
string reason = 2;
|
||||
}
|
||||
|
||||
enum Status {
|
||||
OK = 0;
|
||||
ERROR = 1;
|
||||
UNPLAYABLE = 2;
|
||||
LOGIN_REQUIRED = 3;
|
||||
CONTENT_CHECK_REQUIRED = 4;
|
||||
AGE_CHECK_REQUIRED = 5;
|
||||
LIVE_STREAM_OFFLINE = 6;
|
||||
FULLSCREEN_ONLY = 7;
|
||||
GL_PLAYBACK_REQUIRED = 8;
|
||||
AGE_VERIFICATION_REQUIRED = 9;
|
||||
}
|
||||
|
||||
message StreamingData {
|
||||
repeated Format formats = 2;
|
||||
repeated Format adaptiveFormats = 3;
|
||||
string serverAbrStreamingUrl = 15;
|
||||
}
|
||||
|
||||
message Format {
|
||||
string url = 2;
|
||||
string signatureCipher = 48;
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
syntax = "proto3";
|
||||
|
||||
import "app/revanced/extension/shared/innertube/player_response.proto";
|
||||
|
||||
package app.revanced.extension.shared.innertube;
|
||||
|
||||
option optimize_for = LITE_RUNTIME;
|
||||
option java_package = "app.revanced.extension.shared.innertube";
|
||||
|
||||
message ReelItemWatchResponse {
|
||||
oneof data {
|
||||
PlayerResponse player_response = 4;
|
||||
}
|
||||
}
|
||||
|
|
@ -43,8 +43,8 @@ public class SpoofVideoStreamsPatch {
|
|||
}
|
||||
|
||||
List<ClientType> availableClients = List.of(
|
||||
ANDROID_VR_1_43_32,
|
||||
ANDROID_REEL,
|
||||
ANDROID_VR_1_43_32,
|
||||
VISIONOS,
|
||||
ANDROID_CREATOR
|
||||
);
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import app.revanced.extension.shared.spoof.ClientType;
|
|||
import app.revanced.extension.youtube.settings.Settings;
|
||||
|
||||
@SuppressWarnings({"deprecation", "unused"})
|
||||
public class SpoofStreamingDataSideEffectsPreference extends Preference {
|
||||
public class SpoofVideoStreamsSideEffectsPreference extends Preference {
|
||||
|
||||
@Nullable
|
||||
private ClientType currentClientType;
|
||||
|
|
@ -33,19 +33,19 @@ public class SpoofStreamingDataSideEffectsPreference extends Preference {
|
|||
Utils.runOnMainThread(this::updateUI);
|
||||
};
|
||||
|
||||
public SpoofStreamingDataSideEffectsPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
public SpoofVideoStreamsSideEffectsPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
public SpoofStreamingDataSideEffectsPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
public SpoofVideoStreamsSideEffectsPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public SpoofStreamingDataSideEffectsPreference(Context context, AttributeSet attrs) {
|
||||
public SpoofVideoStreamsSideEffectsPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public SpoofStreamingDataSideEffectsPreference(Context context) {
|
||||
public SpoofVideoStreamsSideEffectsPreference(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
|
|
@ -92,12 +92,13 @@ public class SpoofStreamingDataSideEffectsPreference extends Preference {
|
|||
summary = str("revanced_spoof_video_streams_about_playback_failure");
|
||||
// VR 1.61 is not exposed in the UI and should never be reached here.
|
||||
case ANDROID_VR_1_43_32, ANDROID_VR_1_61_48 ->
|
||||
summary = str("revanced_spoof_video_streams_about_no_audio_tracks")
|
||||
+ '\n' + str("revanced_spoof_video_streams_about_no_stable_volume");
|
||||
case VISIONOS ->
|
||||
summary = str("revanced_spoof_video_streams_about_experimental")
|
||||
summary = str("revanced_spoof_video_streams_about_playback_failure")
|
||||
+ '\n' + str("revanced_spoof_video_streams_about_no_audio_tracks")
|
||||
+ '\n' + str("revanced_spoof_video_streams_about_no_av1");
|
||||
+ '\n' + str("revanced_spoof_video_streams_about_no_stable_volume");
|
||||
case VISIONOS -> summary = str("revanced_spoof_video_streams_about_experimental")
|
||||
+ '\n' + str("revanced_spoof_video_streams_about_playback_failure")
|
||||
+ '\n' + str("revanced_spoof_video_streams_about_no_audio_tracks")
|
||||
+ '\n' + str("revanced_spoof_video_streams_about_no_av1");
|
||||
default -> Logger.printException(() -> "Unknown client: " + clientType);
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue