diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/settings/YouTubeMusicHostActivity.java b/extensions/shared/src/main/java/app/revanced/extension/music/settings/YouTubeMusicHostActivity.java
index 6378a065e..1e884b7ba 100644
--- a/extensions/shared/src/main/java/app/revanced/extension/music/settings/YouTubeMusicHostActivity.java
+++ b/extensions/shared/src/main/java/app/revanced/extension/music/settings/YouTubeMusicHostActivity.java
@@ -27,9 +27,9 @@ public class YouTubeMusicHostActivity extends BaseHostActivity {
}
@Override
- public void finish() {
- if (!handleFinish()) {
- super.finish();
+ public void onBackPressed() {
+ if (handleBackPress()) {
+ super.onBackPressed();
}
}
@@ -106,14 +106,10 @@ public class YouTubeMusicHostActivity extends BaseHostActivity {
}
/**
- * Injection point.
- *
- * Overrides {@link Activity#finish()} of the injection Activity.
- *
- * @return if the original activity finish method should be allowed to run.
+ * @return Whether pressing the back button should be allowed.
*/
@Override
- protected boolean handleFinish() {
- return YouTubeMusicSearchViewController.handleFinish(searchViewController);
+ protected boolean handleBackPress() {
+ return YouTubeMusicSearchViewController.handleBackPress(searchViewController);
}
}
diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/settings/search/YouTubeMusicSearchViewController.java b/extensions/shared/src/main/java/app/revanced/extension/music/settings/search/YouTubeMusicSearchViewController.java
index 3bec2e500..49f1ff3d6 100644
--- a/extensions/shared/src/main/java/app/revanced/extension/music/settings/search/YouTubeMusicSearchViewController.java
+++ b/extensions/shared/src/main/java/app/revanced/extension/music/settings/search/YouTubeMusicSearchViewController.java
@@ -41,13 +41,12 @@ public class YouTubeMusicSearchViewController extends BaseSearchViewController {
// This method can be empty or handle music-specific preferences if any.
}
- // Static method for handling Activity finish
- public static boolean handleFinish(YouTubeMusicSearchViewController searchViewController) {
+ public static boolean handleBackPress(YouTubeMusicSearchViewController searchViewController) {
if (searchViewController != null && searchViewController.isSearchActive()) {
searchViewController.closeSearch();
- return true;
+ return false;
}
- return false;
+ return true;
}
// Adapter to wrap YouTubeMusicPreferenceFragment to BasePreferenceFragment interface.
diff --git a/extensions/shared/src/main/java/app/revanced/extension/shared/innertube/client/YouTubeClient.kt b/extensions/shared/src/main/java/app/revanced/extension/shared/innertube/client/YouTubeClient.kt
index 59f99e091..aa8ebbcfa 100644
--- a/extensions/shared/src/main/java/app/revanced/extension/shared/innertube/client/YouTubeClient.kt
+++ b/extensions/shared/src/main/java/app/revanced/extension/shared/innertube/client/YouTubeClient.kt
@@ -203,6 +203,11 @@ object YouTubeClient {
* 2. Over the past few months, BotGuard PoToken has not been used on WEB_SAFARI (iPadOS).
*
* It may still not work for some users.
+ *
+ * Update:
+ * Playback issue occurs when the value of 'iosPlayerConfig.requestIosguardDataAfterPlaybackStarts' in the player response is 'true'.
+ * It appears that blocking the '/att/get' endpoint can prevent this, but I'm not sure.
+ * (More samples are needed)
*/
private const val PACKAGE_NAME_IPADOS = "com.google.ios.youtube"
@@ -252,16 +257,6 @@ object YouTubeClient {
*/
private const val CLIENT_VERSION_TVHTML5 = "7.20250917.13.00"
private const val USER_AGENT_TVHTML5 =
- "Mozilla/5.0(SMART-TV; Linux; Tizen 4.0.0.2) AppleWebkit/605.1.15 (KHTML, like Gecko) SamsungBrowser/9.2 TV Safari/605.1.15"
-
-
- // TVHTML5 SIMPLY
- /**
- * Video not playable: None.
- * Note: Only 'Authorization' is supported.
- */
- private const val CLIENT_VERSION_TVHTML5_SIMPLY = "1.0"
- private const val USER_AGENT_TVHTML5_SIMPLY =
"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0; Xbox)"
@@ -512,34 +507,13 @@ object YouTubeClient {
TV(
id = 7,
clientVersion = CLIENT_VERSION_TVHTML5,
- clientPlatform = CLIENT_PLATFORM_TV,
+ clientPlatform = CLIENT_PLATFORM_GAME_CONSOLE,
userAgent = USER_AGENT_TVHTML5,
requireJS = true,
refererFormat = CLIENT_REFERER_FORMAT_TV,
clientName = "TVHTML5",
friendlyName = "TV"
),
- TV_SIMPLY(
- id = 75,
- clientVersion = CLIENT_VERSION_TVHTML5_SIMPLY,
- clientPlatform = CLIENT_PLATFORM_GAME_CONSOLE,
- userAgent = USER_AGENT_TVHTML5_SIMPLY,
- requireJS = true,
- clientName = "TVHTML5_SIMPLY",
- refererFormat = CLIENT_REFERER_FORMAT_TV,
- friendlyName = "TV Simply"
- ),
- TV_SIMPLY_NO_AUTH(
- id = 75,
- clientVersion = CLIENT_VERSION_TVHTML5_SIMPLY,
- clientPlatform = CLIENT_PLATFORM_GAME_CONSOLE,
- userAgent = USER_AGENT_TVHTML5_SIMPLY,
- requireJS = true,
- clientName = "TVHTML5_SIMPLY",
- refererFormat = CLIENT_REFERER_FORMAT_TV,
- supportsCookies = false,
- friendlyName = "TV Simply No Auth"
- ),
// Unused client.
TV_EMBEDDED(
@@ -580,17 +554,15 @@ object YouTubeClient {
ANDROID_CREATOR,
IPADOS,
TV,
- TV_SIMPLY,
MWEB,
IOS_DEPRECATED,
)
val CLIENT_ORDER_TO_USE_JS_PREFER_TV: Array = arrayOf(
- TV_SIMPLY,
+ TV,
ANDROID_VR,
VISIONOS,
ANDROID_CREATOR,
IPADOS,
- TV,
MWEB,
IOS_DEPRECATED,
)
diff --git a/extensions/shared/src/main/java/app/revanced/extension/shared/innertube/utils/ThrottlingParameterUtils.java b/extensions/shared/src/main/java/app/revanced/extension/shared/innertube/utils/ThrottlingParameterUtils.java
index 7ac69f741..b48c348da 100644
--- a/extensions/shared/src/main/java/app/revanced/extension/shared/innertube/utils/ThrottlingParameterUtils.java
+++ b/extensions/shared/src/main/java/app/revanced/extension/shared/innertube/utils/ThrottlingParameterUtils.java
@@ -62,11 +62,11 @@ public class ThrottlingParameterUtils {
/**
* Hardcoded javascript url path (Mobile Web).
*/
- private static final String PLAYER_JS_HARDCODED_URL_PATH_MOBILE_WEB = "b66835e2";
+ private static final String PLAYER_JS_HARDCODED_URL_PATH_MOBILE_WEB = "0004de42";
/**
* Hardcoded javascript url path (TV).
*/
- private static final String PLAYER_JS_HARDCODED_URL_PATH_TV = "b66835e2";
+ private static final String PLAYER_JS_HARDCODED_URL_PATH_TV = "0004de42";
/**
* Regular expression pattern to find variables used in JavaScript url.
*/
diff --git a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/SpoofStreamingDataPatch.java b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/SpoofStreamingDataPatch.java
index 4d16df70e..11af082ff 100644
--- a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/SpoofStreamingDataPatch.java
+++ b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/SpoofStreamingDataPatch.java
@@ -332,7 +332,7 @@ public class SpoofStreamingDataPatch {
*
* It takes about 3-5 seconds to download the JavaScript and initialize the Cipher class.
* Initialize it before the video starts.
- * Used for {@link ClientType#TV}, {@link ClientType#TV_SIMPLY} and {@link ClientType#TV_EMBEDDED}.
+ * Used for {@link ClientType#TV} and {@link ClientType#TV_EMBEDDED}.
*/
public static void initializeJavascript() {
if (SPOOF_STREAMING_DATA_USE_JS) {
diff --git a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/requests/StreamingDataRequest.kt b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/requests/StreamingDataRequest.kt
index f82029d6a..7f9ad715c 100644
--- a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/requests/StreamingDataRequest.kt
+++ b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/requests/StreamingDataRequest.kt
@@ -358,100 +358,96 @@ class StreamingDataRequest private constructor(
private fun setAudioTrackLanguage(
videoId: String,
requestHeader: Map,
+ isMobileWeb: Boolean,
) {
- for (clientType in arrayOf(ClientType.ANDROID, ClientType.TV_SIMPLY_NO_AUTH, ClientType.ANDROID_VR)) {
- val startTime = System.currentTimeMillis()
- Logger.printDebug { "Fetching audio track for: $videoId using client: $clientType" }
+ // On Mobile Web, if an invalid language code is used, it will fall back to English.
+ // Only use the YouTube API to fetch the default audio track language code when the client is Mobile Web.
+ if (isMobileWeb) {
+ for (clientType in arrayOf(ClientType.ANDROID, ClientType.ANDROID_VR)) {
+ val startTime = System.currentTimeMillis()
+ Logger.printDebug { "Fetching audio track for: $videoId using client: $clientType" }
- try {
- val requestBody = if (clientType.requireJS) {
- createJSRequestBody(
- clientType = clientType,
- videoId = videoId,
- language = "en",
- isGVS = false,
- )
- } else {
- createApplicationRequestBody(
+ try {
+ val requestBody = createApplicationRequestBody(
clientType = clientType,
videoId = videoId,
language = "en"
)
- }
- val connection =
- getInnerTubeResponseConnectionFromRoute(
- GET_AUDIO_TRACK,
- clientType,
- requestHeader
- )
+ val connection =
+ getInnerTubeResponseConnectionFromRoute(
+ GET_AUDIO_TRACK,
+ clientType,
+ requestHeader
+ )
- connection.setFixedLengthStreamingMode(requestBody.size)
- connection.outputStream.write(requestBody)
+ connection.setFixedLengthStreamingMode(requestBody.size)
+ connection.outputStream.write(requestBody)
- val responseCode = connection.responseCode
- if (responseCode != 200) {
- connection.disconnect()
- continue
- }
- val json = Requester.parseJSONObjectAndDisconnect(connection)
+ val responseCode = connection.responseCode
+ if (responseCode != 200) {
+ connection.disconnect()
+ continue
+ }
+ val json = Requester.parseJSONObjectAndDisconnect(connection)
- if (!json.has("captions")) {
- continue
- }
- val captions =
- json.getJSONObject("captions")
- if (!captions.has("playerCaptionsTracklistRenderer")) {
- continue
- }
- val playerCaptionsTracklistRenderer =
- captions.getJSONObject("playerCaptionsTracklistRenderer")
- if (!playerCaptionsTracklistRenderer.has("audioTracks")) {
- continue
- }
- val audioTracks =
- playerCaptionsTracklistRenderer.getJSONArray("audioTracks")
- if (audioTracks.length() < 2) {
- continue
- }
- if (!json.has("streamingData")) {
- continue
- }
- val streamingData =
- json.getJSONObject("streamingData")
- if (!streamingData.has("adaptiveFormats")) {
- continue
- }
- val adaptiveFormats =
- streamingData.getJSONArray("adaptiveFormats")
- if (adaptiveFormats.length() < 2) {
- continue
- }
- for (i in adaptiveFormats.length() - 1 downTo 0) {
- val adaptiveFormat = adaptiveFormats.getJSONObject(i)
+ if (!json.has("captions")) {
+ continue
+ }
+ val captions =
+ json.getJSONObject("captions")
+ if (!captions.has("playerCaptionsTracklistRenderer")) {
+ continue
+ }
+ val playerCaptionsTracklistRenderer =
+ captions.getJSONObject("playerCaptionsTracklistRenderer")
+ if (!playerCaptionsTracklistRenderer.has("audioTracks")) {
+ continue
+ }
+ val audioTracks =
+ playerCaptionsTracklistRenderer.getJSONArray("audioTracks")
+ if (audioTracks.length() < 2) {
+ continue
+ }
+ if (!json.has("streamingData")) {
+ continue
+ }
+ val streamingData =
+ json.getJSONObject("streamingData")
+ if (!streamingData.has("adaptiveFormats")) {
+ continue
+ }
+ val adaptiveFormats =
+ streamingData.getJSONArray("adaptiveFormats")
+ if (adaptiveFormats.length() < 2) {
+ continue
+ }
+ for (i in adaptiveFormats.length() - 1 downTo 0) {
+ val adaptiveFormat = adaptiveFormats.getJSONObject(i)
- if (adaptiveFormat.has("audioTrack")) {
- val audioTrack = adaptiveFormat.getJSONObject("audioTrack")
- val displayName = audioTrack.getString("displayName")
- if (StringUtils.contains(displayName, "original")) {
- val id = audioTrack.getString("id")
- val dotIndex = StringUtils.indexOf(id, ".")
- if (dotIndex > -1) {
- val languageCode = id.substring(0, dotIndex)
- overrideLanguage = languageCode
- return
+ if (adaptiveFormat.has("audioTrack")) {
+ val audioTrack = adaptiveFormat.getJSONObject("audioTrack")
+ val displayName = audioTrack.getString("displayName")
+ if (StringUtils.contains(displayName, "original")) {
+ val id = audioTrack.getString("id")
+ val dotIndex = StringUtils.indexOf(id, ".")
+ if (dotIndex > -1) {
+ val languageCode = id.substring(0, dotIndex)
+ overrideLanguage = languageCode
+ return
+ }
}
}
}
+ } catch (ex: SocketTimeoutException) {
+ handleConnectionError("Connection timeout", ex)
+ } catch (ex: IOException) {
+ handleConnectionError("Network error", ex)
+ } catch (ex: Exception) {
+ Logger.printException({ "send failed" }, ex)
+ } finally {
+ Logger.printDebug { "Fetching audio track request end (videoId: $videoId, took: ${(System.currentTimeMillis() - startTime)} ms)" }
}
- } catch (ex: SocketTimeoutException) {
- handleConnectionError("Connection timeout", ex)
- } catch (ex: IOException) {
- handleConnectionError("Network error", ex)
- } catch (ex: Exception) {
- Logger.printException({ "send failed" }, ex)
- } finally {
- Logger.printDebug { "Fetching audio track request end (videoId: $videoId, took: ${(System.currentTimeMillis() - startTime)} ms)" }
}
}
if (overrideLanguage.isEmpty()) {
@@ -551,7 +547,11 @@ class StreamingDataRequest private constructor(
if (!clientType.supportsCookies
&& BaseSettings.DISABLE_AUTO_AUDIO_TRACKS.get()
&& overrideLanguage.isEmpty()) {
- setAudioTrackLanguage(videoId, requestHeader)
+ setAudioTrackLanguage(
+ videoId,
+ requestHeader,
+ clientType == ClientType.MWEB
+ )
}
val connection = send(
diff --git a/extensions/shared/src/main/java/app/revanced/extension/shared/settings/BaseHostActivity.java b/extensions/shared/src/main/java/app/revanced/extension/shared/settings/BaseHostActivity.java
index a48884cb1..42f5d9319 100644
--- a/extensions/shared/src/main/java/app/revanced/extension/shared/settings/BaseHostActivity.java
+++ b/extensions/shared/src/main/java/app/revanced/extension/shared/settings/BaseHostActivity.java
@@ -178,5 +178,5 @@ public abstract class BaseHostActivity extends Activity {
*/
protected void onPostToolbarSetup(Activity activity, Toolbar toolbar, PreferenceFragment fragment) {}
- protected abstract boolean handleFinish();
+ protected abstract boolean handleBackPress();
}
diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/player/PlayerPatch.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/player/PlayerPatch.java
index cbbeddeea..152aef2f6 100644
--- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/player/PlayerPatch.java
+++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/player/PlayerPatch.java
@@ -9,6 +9,7 @@ import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewStub;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.RelativeLayout;
@@ -646,6 +647,13 @@ public class PlayerPatch {
return Settings.HIDE_SEEK_MESSAGE.get();
}
+ @Nullable
+ public static ViewStub hideSeekMessage(ViewStub viewStub) {
+ return Settings.HIDE_SEEK_MESSAGE.get()
+ ? null
+ : viewStub;
+ }
+
public static boolean hideSeekUndoMessage() {
return Settings.HIDE_SEEK_UNDO_MESSAGE.get();
}
diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/swipe/SwipeControlsPatch.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/swipe/SwipeControlsPatch.java
index a904ef349..d3ff1e8ad 100644
--- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/swipe/SwipeControlsPatch.java
+++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/swipe/SwipeControlsPatch.java
@@ -74,6 +74,11 @@ public class SwipeControlsPatch {
return Settings.SWIPE_SPEED.get() &&
!Settings.SWIPE_OVERLAY_STYLE.get().isLegacy();
}
+
+ @Override
+ public List> getParentSettings() {
+ return List.of(Settings.SWIPE_SPEED, Settings.SWIPE_OVERLAY_STYLE);
+ }
}
public static final class SwipeOverlaySeekColorAvailability implements Setting.Availability {
@@ -82,5 +87,10 @@ public class SwipeControlsPatch {
return Settings.SWIPE_SEEK.get() &&
!Settings.SWIPE_OVERLAY_STYLE.get().isLegacy();
}
+
+ @Override
+ public List> getParentSettings() {
+ return List.of(Settings.SWIPE_SEEK, Settings.SWIPE_OVERLAY_STYLE);
+ }
}
}
diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/Settings.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/Settings.java
index 9a0e877d1..65a3dbbf2 100644
--- a/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/Settings.java
+++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/Settings.java
@@ -741,6 +741,18 @@ public class Settings extends BaseSettings {
public static final BooleanSetting SB_HIDE_EXPORT_WARNING = new BooleanSetting("sb_hide_export_warning", FALSE, false, false);
public static final BooleanSetting SB_SEEN_GUIDELINES = new BooleanSetting("sb_seen_guidelines", FALSE, false, false);
+ // Deprecated migrations
+ private static final FloatSetting DEPRECATED_SB_CATEGORY_SPONSOR_OPACITY = new FloatSetting("sb_sponsor_opacity", 0.8f, false, false);
+ private static final FloatSetting DEPRECATED_SB_CATEGORY_SELF_PROMO_OPACITY = new FloatSetting("sb_selfpromo_opacity", 0.8f, false, false);
+ private static final FloatSetting DEPRECATED_SB_CATEGORY_INTERACTION_OPACITY = new FloatSetting("sb_interaction_opacity", 0.8f, false, false);
+ private static final FloatSetting DEPRECATED_SB_CATEGORY_HIGHLIGHT_OPACITY = new FloatSetting("sb_highlight_opacity", 0.8f, false, false);
+ private static final FloatSetting DEPRECATED_SB_CATEGORY_HOOK_OPACITY = new FloatSetting("sb_hook_opacity", 0.8f, false, false);
+ private static final FloatSetting DEPRECATED_SB_CATEGORY_INTRO_OPACITY = new FloatSetting("sb_intro_opacity", 0.8f, false, false);
+ private static final FloatSetting DEPRECATED_SB_CATEGORY_OUTRO_OPACITY = new FloatSetting("sb_outro_opacity", 0.8f, false, false);
+ private static final FloatSetting DEPRECATED_SB_CATEGORY_PREVIEW_OPACITY = new FloatSetting("sb_preview_opacity", 0.8f, false, false);
+ private static final FloatSetting DEPRECATED_SB_CATEGORY_FILLER_OPACITY = new FloatSetting("sb_filler_opacity", 0.8f, false, false);
+ private static final FloatSetting DEPRECATED_SB_CATEGORY_MUSIC_OFFTOPIC_OPACITY = new FloatSetting("sb_music_offtopic_opacity", 0.8f, false, false);
+
static {
// region Migration initialized
@@ -798,6 +810,19 @@ public class Settings extends BaseSettings {
migrateFromOldPreferences(ytPrefs, setting, key);
}
}
+
+ // Migrate old saved data. Must be done here before the settings can be used by any other code.
+ applyOldSbOpacityToColor(SB_CATEGORY_SPONSOR_COLOR, DEPRECATED_SB_CATEGORY_SPONSOR_OPACITY);
+ applyOldSbOpacityToColor(SB_CATEGORY_SELF_PROMO_COLOR, DEPRECATED_SB_CATEGORY_SELF_PROMO_OPACITY);
+ applyOldSbOpacityToColor(SB_CATEGORY_INTERACTION_COLOR, DEPRECATED_SB_CATEGORY_INTERACTION_OPACITY);
+ applyOldSbOpacityToColor(SB_CATEGORY_HIGHLIGHT_COLOR, DEPRECATED_SB_CATEGORY_HIGHLIGHT_OPACITY);
+ applyOldSbOpacityToColor(SB_CATEGORY_HOOK_COLOR, DEPRECATED_SB_CATEGORY_HOOK_OPACITY);
+ applyOldSbOpacityToColor(SB_CATEGORY_INTRO_COLOR, DEPRECATED_SB_CATEGORY_INTRO_OPACITY);
+ applyOldSbOpacityToColor(SB_CATEGORY_OUTRO_COLOR, DEPRECATED_SB_CATEGORY_OUTRO_OPACITY);
+ applyOldSbOpacityToColor(SB_CATEGORY_PREVIEW_COLOR, DEPRECATED_SB_CATEGORY_PREVIEW_OPACITY);
+ applyOldSbOpacityToColor(SB_CATEGORY_FILLER_COLOR, DEPRECATED_SB_CATEGORY_FILLER_OPACITY);
+ applyOldSbOpacityToColor(SB_CATEGORY_MUSIC_OFFTOPIC_COLOR, DEPRECATED_SB_CATEGORY_MUSIC_OFFTOPIC_OPACITY);
+
// endregion
// region SB import/export callbacks
@@ -806,4 +831,13 @@ public class Settings extends BaseSettings {
// endregion
}
+
+ private static void applyOldSbOpacityToColor(StringSetting colorSetting, FloatSetting opacitySetting) {
+ String colorString = colorSetting.get();
+ if (colorString.length() >= 8) {
+ return; // Color is already #ARGB
+ }
+
+ colorSetting.save(SponsorBlockSettings.migrateOldColorString(colorString, opacitySetting.get()));
+ }
}
diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/WebViewHostActivity.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/WebViewHostActivity.java
index 1c2aa5ecc..a1ce16401 100644
--- a/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/WebViewHostActivity.java
+++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/WebViewHostActivity.java
@@ -31,6 +31,7 @@ import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toolbar;
+import android.window.OnBackInvokedDispatcher;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -141,7 +142,12 @@ public class WebViewHostActivity extends Activity {
WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS);
}
}
-
+ }
+ if (isSDKAbove(33)) {
+ getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
+ OnBackInvokedDispatcher.PRIORITY_DEFAULT,
+ this::finish
+ );
}
webView = findViewById(getIdIdentifier("revanced_webview"));
@@ -207,7 +213,7 @@ public class WebViewHostActivity extends Activity {
toolbar = new Toolbar(toolBarParent.getContext());
toolbar.setBackgroundColor(Color.WHITE);
toolbar.setNavigationIcon(ThemeUtils.getBackButtonDrawable(false));
- toolbar.setNavigationOnClickListener(view -> this.onBackPressed());
+ toolbar.setNavigationOnClickListener(view -> this.finish());
toolbar.setTitle(toolbarLabel);
final int margin = Utils.dipToPixels(16);
toolbar.setTitleMarginStart(margin);
diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/YouTubeActivityHook.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/YouTubeActivityHook.java
index 17cbf5465..7c548d157 100644
--- a/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/YouTubeActivityHook.java
+++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/YouTubeActivityHook.java
@@ -144,14 +144,10 @@ public class YouTubeActivityHook extends BaseActivityHook {
}
/**
- * Injection point.
- *
- * Overrides {@link Activity#finish()} of the injection Activity.
- *
- * @return if the original activity finish method should be allowed to run.
+ * @return Whether pressing the back button should be allowed.
*/
@SuppressWarnings("unused")
- public static boolean handleFinish() {
- return YouTubeSearchViewController.handleFinish(searchViewController);
+ public static boolean handleBackPress() {
+ return YouTubeSearchViewController.handleBackPress(searchViewController);
}
}
diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/YouTubeHostActivity.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/YouTubeHostActivity.java
index dd91425bb..173a90e5d 100644
--- a/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/YouTubeHostActivity.java
+++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/YouTubeHostActivity.java
@@ -28,9 +28,9 @@ public class YouTubeHostActivity extends BaseHostActivity {
}
@Override
- public void finish() {
- if (!handleFinish()) {
- super.finish();
+ public void onBackPressed() {
+ if (handleBackPress()) {
+ super.onBackPressed();
}
}
@@ -98,14 +98,10 @@ public class YouTubeHostActivity extends BaseHostActivity {
}
/**
- * Injection point.
- *
- * Overrides {@link Activity#finish()} of the injection Activity.
- *
- * @return if the original activity finish method should be allowed to run.
+ * @return Whether pressing the back button should be allowed.
*/
@Override
- protected boolean handleFinish() {
- return YouTubeSearchViewController.handleFinish(searchViewController);
+ protected boolean handleBackPress() {
+ return YouTubeSearchViewController.handleBackPress(searchViewController);
}
}
diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/search/YouTubeSearchViewController.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/search/YouTubeSearchViewController.java
index b979994ea..7a0773bc0 100644
--- a/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/search/YouTubeSearchViewController.java
+++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/search/YouTubeSearchViewController.java
@@ -41,13 +41,12 @@ public class YouTubeSearchViewController extends BaseSearchViewController {
protected void setupSpecialPreferenceListeners(BaseSearchResultItem item) {
}
- // Static method for Activity finish.
- public static boolean handleFinish(YouTubeSearchViewController searchViewController) {
+ public static boolean handleBackPress(YouTubeSearchViewController searchViewController) {
if (searchViewController != null && searchViewController.isSearchActive()) {
searchViewController.closeSearch();
- return true;
+ return false;
}
- return false;
+ return true;
}
// Adapter to wrap YouTubePreferenceFragment to BasePreferenceFragment interface.
diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/sponsorblock/SponsorBlockSettings.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/sponsorblock/SponsorBlockSettings.java
index aec746aa6..5d2c5bc69 100644
--- a/extensions/shared/src/main/java/app/revanced/extension/youtube/sponsorblock/SponsorBlockSettings.java
+++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/sponsorblock/SponsorBlockSettings.java
@@ -14,6 +14,7 @@ import androidx.annotation.Nullable;
import org.json.JSONArray;
import org.json.JSONObject;
+import java.util.Locale;
import java.util.UUID;
import app.revanced.extension.shared.settings.Setting;
@@ -261,6 +262,22 @@ public class SponsorBlockSettings {
return uuid;
}
+ public static String migrateOldColorString(String colorString, float opacity) {
+ if (colorString.length() >= 8) {
+ return colorString;
+ }
+
+ // Change color string from #RGB to #ARGB using default alpha.
+ if (colorString.startsWith("#")) {
+ colorString = colorString.substring(1);
+ }
+
+ String alphaHex = String.format(Locale.US, "%02X", (int)(opacity * 255));
+ String argbColorString = '#' + alphaHex + colorString.substring(0, 6);
+ Logger.printDebug(() -> "Migrating old color string with default opacity: " + argbColorString);
+ return argbColorString;
+ }
+
private static boolean initialized;
public static void initialize() {
diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/sponsorblock/objects/SegmentCategoryPreference.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/sponsorblock/objects/SegmentCategoryPreference.java
index 76607ea10..d8a55a91a 100644
--- a/extensions/shared/src/main/java/app/revanced/extension/youtube/sponsorblock/objects/SegmentCategoryPreference.java
+++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/sponsorblock/objects/SegmentCategoryPreference.java
@@ -2,6 +2,7 @@ package app.revanced.extension.youtube.sponsorblock.objects;
import static app.revanced.extension.shared.utils.StringRef.str;
import static app.revanced.extension.shared.utils.Utils.dipToPixels;
+import static app.revanced.extension.youtube.sponsorblock.SponsorBlockSettings.migrateOldColorString;
import android.content.Context;
import android.view.View;
@@ -54,6 +55,10 @@ public class SegmentCategoryPreference extends ColorPickerPreference {
@Override
public final void setText(String colorString) {
try {
+ // Migrate old data imported in the settings UI.
+ // This migration is needed here because pasting into the settings
+ // immediately syncs the data with the preferences.
+ colorString = migrateOldColorString(colorString, SegmentCategory.CATEGORY_DEFAULT_OPACITY);
super.setText(colorString);
// Save to category.
@@ -68,7 +73,8 @@ public class SegmentCategoryPreference extends ColorPickerPreference {
Utils.showToastShort(str("revanced_settings_color_invalid"));
setText(category.colorSetting.defaultValue);
} catch (Exception ex) {
- Logger.printException(() -> "setText failure: " + colorString, ex);
+ String colorStringFinal = colorString;
+ Logger.printException(() -> "setText failure: " + colorStringFinal, ex);
}
}
diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/swipecontrols/controller/gesture/ClassicSwipeController.kt b/extensions/shared/src/main/java/app/revanced/extension/youtube/swipecontrols/controller/gesture/ClassicSwipeController.kt
index dfb642e06..ec45f0926 100644
--- a/extensions/shared/src/main/java/app/revanced/extension/youtube/swipecontrols/controller/gesture/ClassicSwipeController.kt
+++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/swipecontrols/controller/gesture/ClassicSwipeController.kt
@@ -1,8 +1,6 @@
package app.revanced.extension.youtube.swipecontrols.controller.gesture
import android.view.MotionEvent
-import app.revanced.extension.youtube.shared.PlayerControlsVisibilityObserver
-import app.revanced.extension.youtube.shared.PlayerControlsVisibilityObserverImpl
import app.revanced.extension.youtube.swipecontrols.SwipeControlsConfigurationProvider
import app.revanced.extension.youtube.swipecontrols.SwipeControlsHostActivity
import app.revanced.extension.youtube.swipecontrols.controller.gesture.core.BaseGestureController
@@ -18,8 +16,7 @@ import app.revanced.extension.youtube.swipecontrols.misc.toPoint
class ClassicSwipeController(
private val controller: SwipeControlsHostActivity,
private val config: SwipeControlsConfigurationProvider,
-) : BaseGestureController(controller),
- PlayerControlsVisibilityObserver by PlayerControlsVisibilityObserverImpl(controller) {
+) : BaseGestureController(controller) {
/**
* the last event captured in [onDown]
*/
@@ -29,10 +26,6 @@ class ClassicSwipeController(
get() = currentSwipe == SwipeDetector.SwipeDirection.VERTICAL
override fun isInSwipeZone(motionEvent: MotionEvent): Boolean {
- // Fix https://github.com/inotia00/ReVanced_Extended/issues/3052
- if (!isFullscreenEngagementPanelVisible) {
- return false
- }
val inVolumeZone = if (controller.config.enableVolumeControls) {
(motionEvent.toPoint() in controller.zones.volume)
} else {
diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/swipecontrols/controller/gesture/core/BaseGestureController.kt b/extensions/shared/src/main/java/app/revanced/extension/youtube/swipecontrols/controller/gesture/core/BaseGestureController.kt
index cf3f10377..08759ca04 100644
--- a/extensions/shared/src/main/java/app/revanced/extension/youtube/swipecontrols/controller/gesture/core/BaseGestureController.kt
+++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/swipecontrols/controller/gesture/core/BaseGestureController.kt
@@ -2,6 +2,8 @@ package app.revanced.extension.youtube.swipecontrols.controller.gesture.core
import android.view.GestureDetector
import android.view.MotionEvent
+import app.revanced.extension.youtube.shared.PlayerControlsVisibilityObserver
+import app.revanced.extension.youtube.shared.PlayerControlsVisibilityObserverImpl
import app.revanced.extension.youtube.swipecontrols.SwipeControlsHostActivity
/**
@@ -17,6 +19,7 @@ abstract class BaseGestureController(
SwipeDetector by SwipeDetectorImpl(
controller.config.swipeMagnitudeThreshold.toDouble(),
),
+ PlayerControlsVisibilityObserver by PlayerControlsVisibilityObserverImpl(controller),
VolumeAndBrightnessScroller by VolumeAndBrightnessScrollerImpl(
controller.audio,
controller.screen,
@@ -44,6 +47,10 @@ abstract class BaseGestureController(
if (!controller.config.enableSwipeControls) {
return false
}
+ // Fix https://github.com/inotia00/ReVanced_Extended/issues/3052
+ if (isFullscreenEngagementPanelVisible) {
+ return false
+ }
// create a copy of the event so we can modify it
// without causing any issues downstream
diff --git a/extensions/shared/src/main/java/com/liskovsoft/youtubeapi/app/nsigsolver/common/Cache.kt b/extensions/shared/src/main/java/com/liskovsoft/youtubeapi/app/nsigsolver/common/Cache.kt
new file mode 100644
index 000000000..ff858690b
--- /dev/null
+++ b/extensions/shared/src/main/java/com/liskovsoft/youtubeapi/app/nsigsolver/common/Cache.kt
@@ -0,0 +1,11 @@
+package com.liskovsoft.youtubeapi.app.nsigsolver.common
+
+internal class Cache {
+ fun load(section: String, key: String): String? {
+ return null
+ }
+
+ fun store(section: String, key: String, content: String) {
+
+ }
+}
\ No newline at end of file
diff --git a/extensions/shared/src/main/java/com/liskovsoft/youtubeapi/app/nsigsolver/common/InfoExtractor.kt b/extensions/shared/src/main/java/com/liskovsoft/youtubeapi/app/nsigsolver/common/InfoExtractor.kt
index 6122ebb6b..09563a55c 100644
--- a/extensions/shared/src/main/java/com/liskovsoft/youtubeapi/app/nsigsolver/common/InfoExtractor.kt
+++ b/extensions/shared/src/main/java/com/liskovsoft/youtubeapi/app/nsigsolver/common/InfoExtractor.kt
@@ -2,17 +2,19 @@ package com.liskovsoft.youtubeapi.app.nsigsolver.common
import com.liskovsoft.googlecommon.common.api.FileApi
import com.liskovsoft.googlecommon.common.helpers.RetrofitHelper
+import com.liskovsoft.youtubeapi.app.nsigsolver.provider.IEContentProviderError
import kotlinx.coroutines.delay
internal abstract class InfoExtractor {
private val fileApi = RetrofitHelper.create(FileApi::class.java)
-
- protected suspend fun downloadWebpage(url: String, tries: Int = 1, timeoutMs: Long = 1_000): String? {
+
+ protected suspend fun downloadWebpage(url: String, tries: Int = 1, timeoutMs: Long = 1_000, errorMsg: String? = null): String {
var tryCount = 0
while (true) {
try {
- return RetrofitHelper.get(fileApi.getContent(url))?.content
+ val content = RetrofitHelper.get(fileApi.getContent(url))?.content
+ return content ?: throw IEContentProviderError(errorMsg ?: "Empty content received for the $url")
} catch (e: Exception) {
tryCount++
if (tryCount >= tries)
diff --git a/extensions/shared/src/main/java/com/liskovsoft/youtubeapi/app/nsigsolver/common/YTInfoExtractor.kt b/extensions/shared/src/main/java/com/liskovsoft/youtubeapi/app/nsigsolver/common/YTInfoExtractor.kt
index 7659acda5..d4f280f35 100644
--- a/extensions/shared/src/main/java/com/liskovsoft/youtubeapi/app/nsigsolver/common/YTInfoExtractor.kt
+++ b/extensions/shared/src/main/java/com/liskovsoft/youtubeapi/app/nsigsolver/common/YTInfoExtractor.kt
@@ -3,12 +3,14 @@ package com.liskovsoft.youtubeapi.app.nsigsolver.common
import kotlinx.coroutines.runBlocking
internal object YTInfoExtractor: InfoExtractor() {
- // TODO: implement caching to the file
- fun loadPlayer(playerUrl: String): String? = runBlocking {
+ val cache: Cache = Cache()
+
+ // TODO: implement caching to the local storage
+ fun loadPlayer(playerUrl: String): String = runBlocking {
return@runBlocking downloadWebpage(playerUrl)
}
- fun downloadWebpageWithRetries(url: String) = runBlocking {
- return@runBlocking downloadWebpage(url, tries = 3)
+ fun downloadWebpageWithRetries(url: String, errorMsg: String? = null): String = runBlocking {
+ return@runBlocking downloadWebpage(url, tries = 3, errorMsg = errorMsg)
}
}
\ No newline at end of file
diff --git a/extensions/shared/src/main/java/com/liskovsoft/youtubeapi/app/nsigsolver/provider/JsChallengeProvider.kt b/extensions/shared/src/main/java/com/liskovsoft/youtubeapi/app/nsigsolver/provider/JsChallengeProvider.kt
index 27b2dde45..1b2d105e5 100644
--- a/extensions/shared/src/main/java/com/liskovsoft/youtubeapi/app/nsigsolver/provider/JsChallengeProvider.kt
+++ b/extensions/shared/src/main/java/com/liskovsoft/youtubeapi/app/nsigsolver/provider/JsChallengeProvider.kt
@@ -3,6 +3,7 @@ package com.liskovsoft.youtubeapi.app.nsigsolver.provider
import com.liskovsoft.youtubeapi.app.nsigsolver.common.YTInfoExtractor
internal abstract class JsChallengeProvider {
+ protected val ie = YTInfoExtractor
protected abstract val supportedTypes: List
private fun validateRequest(request: JsChallengeRequest) {
@@ -31,11 +32,11 @@ internal abstract class JsChallengeProvider {
/**
* Subclasses can override this method to handle bulk solving
*/
- protected abstract fun realBulkSolve(validatedRequests: List): Sequence
+ protected abstract fun realBulkSolve(requests: List): Sequence
- protected fun getPlayer(videoId: String, playerUrl: String): String? {
+ protected fun getPlayer(videoId: String?, playerUrl: String): String {
return try {
- YTInfoExtractor.loadPlayer(playerUrl)
+ ie.loadPlayer(playerUrl)
} catch (e: Exception) {
throw JsChallengeProviderError("Failed to load player for JS challenge: $playerUrl", e)
}
diff --git a/extensions/shared/src/main/java/com/liskovsoft/youtubeapi/app/nsigsolver/provider/ProviderData.kt b/extensions/shared/src/main/java/com/liskovsoft/youtubeapi/app/nsigsolver/provider/ProviderData.kt
index d218b2246..b5ef1ecd5 100644
--- a/extensions/shared/src/main/java/com/liskovsoft/youtubeapi/app/nsigsolver/provider/ProviderData.kt
+++ b/extensions/shared/src/main/java/com/liskovsoft/youtubeapi/app/nsigsolver/provider/ProviderData.kt
@@ -13,11 +13,11 @@ internal data class JsChallengeRequest(
internal data class ChallengeInput(
val playerUrl: String,
- val challenges: MutableList = mutableListOf()
+ val challenges: List
)
internal data class ChallengeOutput(
- val results: MutableMap = mutableMapOf()
+ val results: Map
)
internal data class JsChallengeProviderResponse(
diff --git a/extensions/shared/src/main/java/com/liskovsoft/youtubeapi/app/nsigsolver/runtime/JsRuntimeChalBaseJCP.kt b/extensions/shared/src/main/java/com/liskovsoft/youtubeapi/app/nsigsolver/runtime/JsRuntimeChalBaseJCP.kt
index 40eab0e21..ee158d5df 100644
--- a/extensions/shared/src/main/java/com/liskovsoft/youtubeapi/app/nsigsolver/runtime/JsRuntimeChalBaseJCP.kt
+++ b/extensions/shared/src/main/java/com/liskovsoft/youtubeapi/app/nsigsolver/runtime/JsRuntimeChalBaseJCP.kt
@@ -1,12 +1,156 @@
package com.liskovsoft.youtubeapi.app.nsigsolver.runtime
+import app.revanced.extension.shared.utils.Logger
+import com.google.gson.Gson
+import com.google.gson.JsonSyntaxException
+import com.liskovsoft.youtubeapi.app.nsigsolver.provider.ChallengeOutput
import com.liskovsoft.youtubeapi.app.nsigsolver.provider.JsChallengeProvider
+import com.liskovsoft.youtubeapi.app.nsigsolver.provider.JsChallengeProviderError
+import com.liskovsoft.youtubeapi.app.nsigsolver.provider.JsChallengeProviderRejectedRequest
+import com.liskovsoft.youtubeapi.app.nsigsolver.provider.JsChallengeProviderResponse
+import com.liskovsoft.youtubeapi.app.nsigsolver.provider.JsChallengeRequest
+import com.liskovsoft.youtubeapi.app.nsigsolver.provider.JsChallengeResponse
import com.liskovsoft.youtubeapi.app.nsigsolver.provider.JsChallengeType
internal abstract class JsRuntimeChalBaseJCP: JsChallengeProvider() {
+ private val tag = JsRuntimeChalBaseJCP::class.simpleName
+ private val cacheSection = "challenge-solver"
+
private val jcpGuideUrl = "https://github.com/yt-dlp/yt-dlp/wiki/YouTube-JS-Challenges"
private val repository = "yt-dlp/yt-dlp-jsc-deno"
+ private val supportedVersion = "0.0.1"
+
override val supportedTypes = listOf(JsChallengeType.N, JsChallengeType.SIG)
-
+ private val scriptFilenames = mapOf(
+ ScriptType.LIB to "lib.js",
+ ScriptType.CORE to "core.js"
+ )
+
+ private val minScriptFilenames = mapOf(
+ ScriptType.LIB to "lib.min.js",
+ ScriptType.CORE to "core.min.js"
+ )
+
+ abstract fun runJsRuntime(stdin: String): String
+
+ override fun realBulkSolve(requests: List): Sequence = sequence {
+ val grouped: Map> = requests.groupBy { it.input.playerUrl }
+
+ for ((playerUrl, groupedRequests) in grouped) {
+ var player = ie.cache.load(cacheSection, "player:$playerUrl")
+
+ val cached = if (player != null) {
+ true
+ } else {
+ val videoId = groupedRequests.firstOrNull()?.videoId
+ player = getPlayer(videoId, playerUrl)
+ false
+ }
+
+ val stdin = constructStdin(player, cached, groupedRequests)
+ val stdout = runJsRuntime(stdin)
+
+ val gson = Gson()
+ val output: SolverOutput = try {
+ gson.fromJson(stdout, solverOutputType)
+ } catch (e: JsonSyntaxException) {
+ throw JsChallengeProviderError("Cannot parse solver output", e)
+ }
+
+ if (output.type == "error")
+ throw JsChallengeProviderError(output.error ?: "Unknown solver output error")
+
+ val preprocessed = output.preprocessed_player
+ if (preprocessed != null)
+ ie.cache.store(cacheSection, "player:$playerUrl", preprocessed)
+
+ for ((request, responseData) in groupedRequests.zip(output.responses)) {
+ if (responseData.type == "error") {
+ yield(JsChallengeProviderResponse(
+ request, null, JsChallengeProviderError(responseData.error ?: "Unknown solver output error")))
+ } else {
+ yield(JsChallengeProviderResponse(
+ request, JsChallengeResponse(request.type, ChallengeOutput(responseData.data))
+ ))
+ }
+ }
+ }
+ }
+
+ private fun constructStdin(player: String, preprocessed: Boolean, requests: List): String {
+ val jsonRequests = requests.map { request ->
+ mapOf(
+ // TODO: i despise nsig name
+ "type" to if (request.type.value == "n") "nsig" else request.type.value,
+ "challenges" to request.input.challenges
+ )
+ }
+ val data = if (preprocessed) {
+ mapOf(
+ "type" to "preprocessed",
+ "preprocessed_player" to player,
+ "requests" to jsonRequests
+ )
+ } else {
+ mapOf(
+ "type" to "player",
+ "player" to player,
+ "requests" to jsonRequests,
+ "output_preprocessed" to true
+ )
+ }
+ val gson = Gson()
+ val jsonData = gson.toJson(data)
+ return """
+ ${libScript.code}
+ const { astring, meriyah } = lib;
+ ${coreScript.code}
+ console.log(JSON.stringify(jsc($jsonData)));
+ """
+ }
+
+ // region: challenge solver script
+
+ private val libScript: Script by lazy {
+ getScript(ScriptType.LIB)
+ }
+
+ private val coreScript: Script by lazy {
+ getScript(ScriptType.CORE)
+ }
+
+ private fun getScript(scriptType: ScriptType): Script {
+ for ((_, fromSource) in iterScriptSources()) {
+ val script = fromSource(scriptType)
+ if (script == null)
+ continue
+ if (script.version != supportedVersion)
+ Logger.printDebug { "Challenge solver ${scriptType.value} script version ${script.version} " +
+ "is not supported (source: ${script.source.value}, supported version: $supportedVersion)" }
+
+ Logger.printDebug { "Using challenge solver ${script.type.value} script v${script.version} " +
+ "(source: ${script.source.value}, variant: ${script.variant.value})" }
+ return script
+ }
+ throw JsChallengeProviderRejectedRequest("No usable challenge solver ${scriptType.value} script available")
+ }
+
+ private fun iterScriptSources(): Sequence Script?>> = sequence {
+ yieldAll(listOf(
+ //Pair(ScriptSource.CACHE, cachedSource),
+ //Pair(ScriptSource.BUILTIN, builtinSource),
+ Pair(ScriptSource.WEB, ::webReleaseSource)
+ ))
+ }
+
+ private fun webReleaseSource(scriptType: ScriptType): Script? {
+ val url = "https://github.com/$repository/releases/download/$supportedVersion/${minScriptFilenames[scriptType]}"
+ val code = ie.downloadWebpageWithRetries(url, "[${tag}] Failed to download challenge solver ${scriptType.value} script")
+ Logger.printDebug { "[${tag}] Downloading challenge solver ${scriptType.value} script from $url" }
+ ie.cache.store(cacheSection, scriptType.value, code)
+ return Script(scriptType, ScriptVariant.MINIFIED, ScriptSource.WEB, supportedVersion, code)
+ }
+
+ // endregion: challenge solver script
}
\ No newline at end of file
diff --git a/extensions/shared/src/main/java/com/liskovsoft/youtubeapi/app/nsigsolver/runtime/JsonData.kt b/extensions/shared/src/main/java/com/liskovsoft/youtubeapi/app/nsigsolver/runtime/JsonData.kt
new file mode 100644
index 000000000..b423b53d7
--- /dev/null
+++ b/extensions/shared/src/main/java/com/liskovsoft/youtubeapi/app/nsigsolver/runtime/JsonData.kt
@@ -0,0 +1,18 @@
+package com.liskovsoft.youtubeapi.app.nsigsolver.runtime
+
+import com.google.gson.reflect.TypeToken
+
+internal val solverOutputType = object : TypeToken() {}.type
+
+internal data class SolverOutput(
+ val type: String,
+ val error: String?,
+ val preprocessed_player: String?,
+ val responses: List
+)
+
+internal data class ResponseData(
+ val type: String,
+ val error: String?,
+ val data: Map
+)
\ No newline at end of file
diff --git a/extensions/shared/src/main/java/com/liskovsoft/youtubeapi/app/nsigsolver/runtime/RuntimeData.kt b/extensions/shared/src/main/java/com/liskovsoft/youtubeapi/app/nsigsolver/runtime/RuntimeData.kt
new file mode 100644
index 000000000..1f4b1d428
--- /dev/null
+++ b/extensions/shared/src/main/java/com/liskovsoft/youtubeapi/app/nsigsolver/runtime/RuntimeData.kt
@@ -0,0 +1,42 @@
+package com.liskovsoft.youtubeapi.app.nsigsolver.runtime
+
+import java.security.MessageDigest
+
+internal enum class ScriptType(val value: String) {
+ LIB("lib"),
+ CORE("core");
+}
+
+internal enum class ScriptVariant(val value: String) {
+ UNKNOWN("unknown"),
+ MINIFIED("minified"),
+ UNMINIFIED("unminified"),
+ DENO_NPM("deno_npm"),
+ BUN_NPM("bun_npm");
+}
+
+internal enum class ScriptSource(val value: String) {
+ PYPACKAGE("python package"),
+ BINARY("binary"),
+ CACHE("cache"),
+ WEB("web"),
+ BUILTIN("builtin");
+}
+
+internal data class Script(
+ val type: ScriptType,
+ val variant: ScriptVariant,
+ val source: ScriptSource,
+ val version: String,
+ val code: String,
+) {
+ val hash: String by lazy {
+ val digest = MessageDigest.getInstance("SHA3-512")
+ val bytes = digest.digest(code.toByteArray(Charsets.UTF_8))
+ bytes.joinToString("") { "%02x".format(it) }
+ }
+
+ override fun toString(): String {
+ return "