mirror of
https://github.com/inotia00/revanced-patches.git
synced 2025-06-13 05:37:40 +02:00
feat(YouTube): Add Open channel of live avatar
patch
This commit is contained in:
@ -44,6 +44,14 @@ object PlayerRoutes {
|
|||||||
"&alt=proto"
|
"&alt=proto"
|
||||||
).compile()
|
).compile()
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
val GET_VIDEO_DETAILS: CompiledRoute = Route(
|
||||||
|
Route.Method.POST,
|
||||||
|
"player" +
|
||||||
|
"?prettyPrint=false" +
|
||||||
|
"&fields=videoDetails.channelId"
|
||||||
|
).compile()
|
||||||
|
|
||||||
private const val YT_API_URL = "https://youtubei.googleapis.com/youtubei/v1/"
|
private const val YT_API_URL = "https://youtubei.googleapis.com/youtubei/v1/"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -0,0 +1,61 @@
|
|||||||
|
package app.revanced.extension.youtube.patches.general;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.utils.Logger;
|
||||||
|
import app.revanced.extension.youtube.patches.general.requests.VideoDetailsRequest;
|
||||||
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
import app.revanced.extension.youtube.utils.VideoUtils;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public final class OpenChannelOfLiveAvatarPatch {
|
||||||
|
private static final boolean OPEN_CHANNEL_OF_LIVE_AVATAR =
|
||||||
|
Settings.OPEN_CHANNEL_OF_LIVE_AVATAR.get();
|
||||||
|
|
||||||
|
private static volatile String videoId = "";
|
||||||
|
private static volatile boolean liveChannelAvatarClicked = false;
|
||||||
|
|
||||||
|
public static void liveChannelAvatarClicked() {
|
||||||
|
liveChannelAvatarClicked = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean openChannelOfLiveAvatar() {
|
||||||
|
try {
|
||||||
|
if (!OPEN_CHANNEL_OF_LIVE_AVATAR) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!liveChannelAvatarClicked) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
VideoDetailsRequest request = VideoDetailsRequest.getRequestForVideoId(videoId);
|
||||||
|
if (request != null) {
|
||||||
|
String channelId = request.getInfo();
|
||||||
|
if (channelId != null) {
|
||||||
|
liveChannelAvatarClicked = false;
|
||||||
|
VideoUtils.openChannel(channelId);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "openChannelOfLiveAvatar failure", ex);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void openChannelOfLiveAvatar(String newlyLoadedVideoId) {
|
||||||
|
try {
|
||||||
|
if (!OPEN_CHANNEL_OF_LIVE_AVATAR) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (newlyLoadedVideoId.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!liveChannelAvatarClicked) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
videoId = newlyLoadedVideoId;
|
||||||
|
VideoDetailsRequest.fetchRequestIfNeeded(newlyLoadedVideoId);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "openChannelOfLiveAvatar failure", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,141 @@
|
|||||||
|
package app.revanced.extension.youtube.patches.general.requests
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import androidx.annotation.GuardedBy
|
||||||
|
import app.revanced.extension.shared.patches.client.WebClient
|
||||||
|
import app.revanced.extension.shared.patches.spoof.requests.PlayerRoutes
|
||||||
|
import app.revanced.extension.shared.requests.Requester
|
||||||
|
import app.revanced.extension.shared.utils.Logger
|
||||||
|
import app.revanced.extension.shared.utils.Utils
|
||||||
|
import org.json.JSONException
|
||||||
|
import org.json.JSONObject
|
||||||
|
import java.io.IOException
|
||||||
|
import java.net.SocketTimeoutException
|
||||||
|
import java.util.Collections
|
||||||
|
import java.util.concurrent.ExecutionException
|
||||||
|
import java.util.concurrent.Future
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
import java.util.concurrent.TimeoutException
|
||||||
|
|
||||||
|
class VideoDetailsRequest private constructor(
|
||||||
|
private val videoId: String
|
||||||
|
) {
|
||||||
|
private val future: Future<String> = Utils.submitOnBackgroundThread {
|
||||||
|
fetch(videoId)
|
||||||
|
}
|
||||||
|
|
||||||
|
val info: String?
|
||||||
|
get() {
|
||||||
|
try {
|
||||||
|
return future[MAX_MILLISECONDS_TO_WAIT_FOR_FETCH, TimeUnit.MILLISECONDS]
|
||||||
|
} catch (ex: TimeoutException) {
|
||||||
|
Logger.printInfo(
|
||||||
|
{ "getInfo timed out" },
|
||||||
|
ex
|
||||||
|
)
|
||||||
|
} catch (ex: InterruptedException) {
|
||||||
|
Logger.printException(
|
||||||
|
{ "getInfo interrupted" },
|
||||||
|
ex
|
||||||
|
)
|
||||||
|
Thread.currentThread().interrupt() // Restore interrupt status flag.
|
||||||
|
} catch (ex: ExecutionException) {
|
||||||
|
Logger.printException(
|
||||||
|
{ "getInfo failure" },
|
||||||
|
ex
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val MAX_MILLISECONDS_TO_WAIT_FOR_FETCH = 20 * 1000L // 20 seconds
|
||||||
|
|
||||||
|
@GuardedBy("itself")
|
||||||
|
val cache: MutableMap<String, VideoDetailsRequest> = Collections.synchronizedMap(
|
||||||
|
object : LinkedHashMap<String, VideoDetailsRequest>(100) {
|
||||||
|
private val CACHE_LIMIT = 50
|
||||||
|
|
||||||
|
override fun removeEldestEntry(eldest: Map.Entry<String, VideoDetailsRequest>): Boolean {
|
||||||
|
return size > CACHE_LIMIT // Evict the oldest entry if over the cache limit.
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
@SuppressLint("ObsoleteSdkInt")
|
||||||
|
fun fetchRequestIfNeeded(videoId: String) {
|
||||||
|
cache[videoId] = VideoDetailsRequest(videoId)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun getRequestForVideoId(videoId: String): VideoDetailsRequest? {
|
||||||
|
synchronized(cache) {
|
||||||
|
return cache[videoId]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleConnectionError(toastMessage: String, ex: Exception?) {
|
||||||
|
Logger.printInfo({ toastMessage }, ex)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sendRequest(videoId: String): JSONObject? {
|
||||||
|
val startTime = System.currentTimeMillis()
|
||||||
|
val clientType = WebClient.ClientType.MWEB
|
||||||
|
val clientTypeName = clientType.name
|
||||||
|
Logger.printDebug { "Fetching video details request for: $videoId, using client: $clientTypeName" }
|
||||||
|
|
||||||
|
try {
|
||||||
|
val connection = PlayerRoutes.getPlayerResponseConnectionFromRoute(
|
||||||
|
PlayerRoutes.GET_VIDEO_DETAILS,
|
||||||
|
clientType
|
||||||
|
)
|
||||||
|
val requestBody =
|
||||||
|
PlayerRoutes.createWebInnertubeBody(clientType, videoId)
|
||||||
|
|
||||||
|
connection.setFixedLengthStreamingMode(requestBody.size)
|
||||||
|
connection.outputStream.write(requestBody)
|
||||||
|
|
||||||
|
val responseCode = connection.responseCode
|
||||||
|
if (responseCode == 200) return Requester.parseJSONObject(connection)
|
||||||
|
|
||||||
|
handleConnectionError(
|
||||||
|
(clientTypeName + " not available with response code: "
|
||||||
|
+ responseCode + " message: " + connection.responseMessage),
|
||||||
|
null
|
||||||
|
)
|
||||||
|
} catch (ex: SocketTimeoutException) {
|
||||||
|
handleConnectionError("Connection timeout", ex)
|
||||||
|
} catch (ex: IOException) {
|
||||||
|
handleConnectionError("Network error", ex)
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
Logger.printException({ "sendRequest failed" }, ex)
|
||||||
|
} finally {
|
||||||
|
Logger.printDebug { "video: " + videoId + " took: " + (System.currentTimeMillis() - startTime) + "ms" }
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseResponse(videoDetailsJson: JSONObject): String? {
|
||||||
|
try {
|
||||||
|
return videoDetailsJson
|
||||||
|
.getJSONObject("videoDetails")
|
||||||
|
.getString("channelId")
|
||||||
|
} catch (e: JSONException) {
|
||||||
|
Logger.printException ({ "Fetch failed while processing response data for response: $videoDetailsJson" }, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fetch(videoId: String): String? {
|
||||||
|
val videoDetailsJson = sendRequest(videoId)
|
||||||
|
if (videoDetailsJson != null) {
|
||||||
|
return parseResponse(videoDetailsJson)
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -156,6 +156,7 @@ public class Settings extends BaseSettings {
|
|||||||
public static final BooleanSetting REMOVE_VIEWER_DISCRETION_DIALOG = new BooleanSetting("revanced_remove_viewer_discretion_dialog", FALSE);
|
public static final BooleanSetting REMOVE_VIEWER_DISCRETION_DIALOG = new BooleanSetting("revanced_remove_viewer_discretion_dialog", FALSE);
|
||||||
|
|
||||||
public static final EnumSetting<FormFactor> CHANGE_LAYOUT = new EnumSetting<>("revanced_change_layout", FormFactor.ORIGINAL, true);
|
public static final EnumSetting<FormFactor> CHANGE_LAYOUT = new EnumSetting<>("revanced_change_layout", FormFactor.ORIGINAL, true);
|
||||||
|
public static final BooleanSetting OPEN_CHANNEL_OF_LIVE_AVATAR = new BooleanSetting("revanced_open_channel_of_live_avatar", FALSE, true);
|
||||||
public static final BooleanSetting SPOOF_APP_VERSION = new BooleanSetting("revanced_spoof_app_version", false, true, "revanced_spoof_app_version_user_dialog_message");
|
public static final BooleanSetting SPOOF_APP_VERSION = new BooleanSetting("revanced_spoof_app_version", false, true, "revanced_spoof_app_version_user_dialog_message");
|
||||||
public static final StringSetting SPOOF_APP_VERSION_TARGET = new StringSetting("revanced_spoof_app_version_target", PatchStatus.SpoofAppVersionDefaultString(), true, parent(SPOOF_APP_VERSION));
|
public static final StringSetting SPOOF_APP_VERSION_TARGET = new StringSetting("revanced_spoof_app_version_target", PatchStatus.SpoofAppVersionDefaultString(), true, parent(SPOOF_APP_VERSION));
|
||||||
|
|
||||||
|
@ -29,12 +29,17 @@ import app.revanced.extension.youtube.shared.VideoInformation;
|
|||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class VideoUtils extends IntentUtils {
|
public class VideoUtils extends IntentUtils {
|
||||||
|
private static final String CHANNEL_URL = "https://www.youtube.com/channel/";
|
||||||
private static final String PLAYLIST_URL = "https://www.youtube.com/playlist?list=";
|
private static final String PLAYLIST_URL = "https://www.youtube.com/playlist?list=";
|
||||||
private static final String VIDEO_URL = "https://youtu.be/";
|
private static final String VIDEO_URL = "https://youtu.be/";
|
||||||
private static final String VIDEO_SCHEME_INTENT_FORMAT = "vnd.youtube://%s?start=%d";
|
private static final String VIDEO_SCHEME_INTENT_FORMAT = "vnd.youtube://%s?start=%d";
|
||||||
private static final String VIDEO_SCHEME_LINK_FORMAT = "https://youtu.be/%s?t=%d";
|
private static final String VIDEO_SCHEME_LINK_FORMAT = "https://youtu.be/%s?t=%d";
|
||||||
private static final AtomicBoolean isExternalDownloaderLaunched = new AtomicBoolean(false);
|
private static final AtomicBoolean isExternalDownloaderLaunched = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
private static String getChannelUrl(String channelId) {
|
||||||
|
return CHANNEL_URL + channelId;
|
||||||
|
}
|
||||||
|
|
||||||
private static String getPlaylistUrl(String playlistId) {
|
private static String getPlaylistUrl(String playlistId) {
|
||||||
return PLAYLIST_URL + playlistId;
|
return PLAYLIST_URL + playlistId;
|
||||||
}
|
}
|
||||||
@ -119,6 +124,10 @@ public class VideoUtils extends IntentUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void openChannel(@NonNull String channelId) {
|
||||||
|
launchView(getChannelUrl(channelId), getContext().getPackageName());
|
||||||
|
}
|
||||||
|
|
||||||
public static void openVideo() {
|
public static void openVideo() {
|
||||||
openVideo(VideoInformation.getVideoId());
|
openVideo(VideoInformation.getVideoId());
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,42 @@
|
|||||||
|
package app.revanced.patches.youtube.general.channel
|
||||||
|
|
||||||
|
import app.revanced.patches.youtube.utils.resourceid.elementsImage
|
||||||
|
import app.revanced.util.fingerprint.legacyFingerprint
|
||||||
|
import app.revanced.util.getReference
|
||||||
|
import app.revanced.util.indexOfFirstInstruction
|
||||||
|
import app.revanced.util.or
|
||||||
|
import com.android.tools.smali.dexlib2.AccessFlags
|
||||||
|
import com.android.tools.smali.dexlib2.Opcode
|
||||||
|
import com.android.tools.smali.dexlib2.iface.Method
|
||||||
|
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||||
|
|
||||||
|
internal val elementsImageFingerprint = legacyFingerprint(
|
||||||
|
name = "elementsImageFingerprint",
|
||||||
|
returnType = "Landroid/view/View;",
|
||||||
|
accessFlags = AccessFlags.PRIVATE or AccessFlags.STATIC,
|
||||||
|
parameters = listOf("Landroid/view/View;"),
|
||||||
|
literals = listOf(elementsImage),
|
||||||
|
)
|
||||||
|
|
||||||
|
internal val clientSettingEndpointFingerprint = legacyFingerprint(
|
||||||
|
name = "clientSettingEndpointFingerprint",
|
||||||
|
returnType = "V",
|
||||||
|
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||||
|
parameters = listOf("L", "Ljava/util/Map;"),
|
||||||
|
strings = listOf(
|
||||||
|
"force_fullscreen",
|
||||||
|
"PLAYBACK_START_DESCRIPTOR_MUTATOR",
|
||||||
|
"VideoPresenterConstants.VIDEO_THUMBNAIL_BITMAP_KEY"
|
||||||
|
),
|
||||||
|
customFingerprint = { method, _ ->
|
||||||
|
indexOfPlaybackStartDescriptorInstruction(method) >= 0
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
internal fun indexOfPlaybackStartDescriptorInstruction(method: Method) =
|
||||||
|
method.indexOfFirstInstruction {
|
||||||
|
val reference = getReference<MethodReference>()
|
||||||
|
opcode == Opcode.INVOKE_VIRTUAL &&
|
||||||
|
reference?.returnType == "Lcom/google/android/libraries/youtube/player/model/PlaybackStartDescriptor;" &&
|
||||||
|
reference.parameterTypes.isEmpty()
|
||||||
|
}
|
@ -0,0 +1,90 @@
|
|||||||
|
package app.revanced.patches.youtube.general.channel
|
||||||
|
|
||||||
|
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||||
|
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||||
|
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
|
||||||
|
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||||
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
|
import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE
|
||||||
|
import app.revanced.patches.youtube.utils.extension.Constants.GENERAL_PATH
|
||||||
|
import app.revanced.patches.youtube.utils.patch.PatchList.OPEN_CHANNEL_OF_LIVE_AVATAR
|
||||||
|
import app.revanced.patches.youtube.utils.resourceid.sharedResourceIdPatch
|
||||||
|
import app.revanced.patches.youtube.utils.settings.ResourceUtils.addPreference
|
||||||
|
import app.revanced.patches.youtube.utils.settings.settingsPatch
|
||||||
|
import app.revanced.patches.youtube.video.playbackstart.playbackStartDescriptorPatch
|
||||||
|
import app.revanced.patches.youtube.video.playbackstart.playbackStartVideoIdReference
|
||||||
|
import app.revanced.util.fingerprint.methodOrThrow
|
||||||
|
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||||
|
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
|
||||||
|
import com.android.tools.smali.dexlib2.Opcode
|
||||||
|
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||||
|
|
||||||
|
private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||||
|
"$GENERAL_PATH/OpenChannelOfLiveAvatarPatch;"
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
val layoutSwitchPatch = bytecodePatch(
|
||||||
|
OPEN_CHANNEL_OF_LIVE_AVATAR.title,
|
||||||
|
OPEN_CHANNEL_OF_LIVE_AVATAR.summary,
|
||||||
|
) {
|
||||||
|
compatibleWith(COMPATIBLE_PACKAGE)
|
||||||
|
|
||||||
|
dependsOn(
|
||||||
|
playbackStartDescriptorPatch,
|
||||||
|
sharedResourceIdPatch,
|
||||||
|
settingsPatch,
|
||||||
|
)
|
||||||
|
|
||||||
|
execute {
|
||||||
|
|
||||||
|
elementsImageFingerprint.methodOrThrow().addInstruction(
|
||||||
|
0,
|
||||||
|
"invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->liveChannelAvatarClicked()V"
|
||||||
|
)
|
||||||
|
|
||||||
|
clientSettingEndpointFingerprint.methodOrThrow().apply {
|
||||||
|
val eqzIndex = indexOfFirstInstructionReversedOrThrow(Opcode.IF_EQZ)
|
||||||
|
var freeIndex = indexOfFirstInstructionReversedOrThrow(eqzIndex, Opcode.NEW_INSTANCE)
|
||||||
|
var freeRegister = getInstruction<OneRegisterInstruction>(freeIndex).registerA
|
||||||
|
|
||||||
|
addInstructionsWithLabels(
|
||||||
|
eqzIndex, """
|
||||||
|
invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->openChannelOfLiveAvatar()Z
|
||||||
|
move-result v$freeRegister
|
||||||
|
if-eqz v$freeRegister, :ignore
|
||||||
|
return-void
|
||||||
|
:ignore
|
||||||
|
nop
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
val playbackStartIndex = indexOfPlaybackStartDescriptorInstruction(this) + 1
|
||||||
|
val playbackStartRegister = getInstruction<OneRegisterInstruction>(playbackStartIndex).registerA
|
||||||
|
|
||||||
|
freeIndex = indexOfFirstInstructionOrThrow(playbackStartIndex, Opcode.CONST_STRING)
|
||||||
|
freeRegister = getInstruction<OneRegisterInstruction>(freeIndex).registerA
|
||||||
|
|
||||||
|
addInstructions(
|
||||||
|
playbackStartIndex + 1, """
|
||||||
|
invoke-virtual { v$playbackStartRegister }, $playbackStartVideoIdReference
|
||||||
|
move-result-object v$freeRegister
|
||||||
|
invoke-static { v$freeRegister }, $EXTENSION_CLASS_DESCRIPTOR->openChannelOfLiveAvatar(Ljava/lang/String;)V
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// region add settings
|
||||||
|
|
||||||
|
addPreference(
|
||||||
|
arrayOf(
|
||||||
|
"PREFERENCE_SCREEN: GENERAL",
|
||||||
|
"PREFERENCE_CATEGORY: GENERAL_EXPERIMENTAL_FLAGS",
|
||||||
|
"SETTINGS: OPEN_CHANNEL_OF_LIVE_AVATAR"
|
||||||
|
),
|
||||||
|
OPEN_CHANNEL_OF_LIVE_AVATAR
|
||||||
|
)
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -169,6 +169,10 @@ internal enum class PatchList(
|
|||||||
"Navigation bar components",
|
"Navigation bar components",
|
||||||
"Adds options to hide or change components related to the navigation bar."
|
"Adds options to hide or change components related to the navigation bar."
|
||||||
),
|
),
|
||||||
|
OPEN_CHANNEL_OF_LIVE_AVATAR(
|
||||||
|
"Open channel of live avatar",
|
||||||
|
"Adds an option to open channel instead of video when clicking on live avatar."
|
||||||
|
),
|
||||||
OPEN_LINKS_EXTERNALLY(
|
OPEN_LINKS_EXTERNALLY(
|
||||||
"Open links externally",
|
"Open links externally",
|
||||||
"Adds an option to always open links in your browser instead of in the in-app-browser."
|
"Adds an option to always open links in your browser instead of in the in-app-browser."
|
||||||
|
@ -83,6 +83,8 @@ var easySeekEduContainer = -1L
|
|||||||
private set
|
private set
|
||||||
var editSettingsAction = -1L
|
var editSettingsAction = -1L
|
||||||
private set
|
private set
|
||||||
|
var elementsImage = -1L
|
||||||
|
private set
|
||||||
var endScreenElementLayoutCircle = -1L
|
var endScreenElementLayoutCircle = -1L
|
||||||
private set
|
private set
|
||||||
var endScreenElementLayoutIcon = -1L
|
var endScreenElementLayoutIcon = -1L
|
||||||
@ -383,6 +385,10 @@ internal val sharedResourceIdPatch = resourcePatch(
|
|||||||
STRING,
|
STRING,
|
||||||
"edit_settings_action"
|
"edit_settings_action"
|
||||||
]
|
]
|
||||||
|
elementsImage = resourceMappings[
|
||||||
|
ID,
|
||||||
|
"elements_image"
|
||||||
|
]
|
||||||
endScreenElementLayoutCircle = resourceMappings[
|
endScreenElementLayoutCircle = resourceMappings[
|
||||||
LAYOUT,
|
LAYOUT,
|
||||||
"endscreen_element_layout_circle"
|
"endscreen_element_layout_circle"
|
||||||
|
@ -0,0 +1,19 @@
|
|||||||
|
package app.revanced.patches.youtube.video.playbackstart
|
||||||
|
|
||||||
|
import app.revanced.util.fingerprint.legacyFingerprint
|
||||||
|
|
||||||
|
const val PLAYBACK_START_DESCRIPTOR_CLASS_DESCRIPTOR =
|
||||||
|
"Lcom/google/android/libraries/youtube/player/model/PlaybackStartDescriptor;"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Purpose of this method is not clear, and it's only used to identify
|
||||||
|
* the obfuscated name of the videoId() method in PlaybackStartDescriptor.
|
||||||
|
*/
|
||||||
|
internal val playbackStartFeatureFlagFingerprint = legacyFingerprint(
|
||||||
|
name = "playbackStartFeatureFlagFingerprint",
|
||||||
|
returnType = "Z",
|
||||||
|
parameters = listOf(PLAYBACK_START_DESCRIPTOR_CLASS_DESCRIPTOR),
|
||||||
|
literals = listOf(45380134L)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
@ -0,0 +1,33 @@
|
|||||||
|
package app.revanced.patches.youtube.video.playbackstart
|
||||||
|
|
||||||
|
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||||
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
|
import app.revanced.patches.youtube.utils.resourceid.sharedResourceIdPatch
|
||||||
|
import app.revanced.util.fingerprint.methodOrThrow
|
||||||
|
import app.revanced.util.getReference
|
||||||
|
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||||
|
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
||||||
|
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||||
|
import com.android.tools.smali.dexlib2.iface.reference.Reference
|
||||||
|
|
||||||
|
internal lateinit var playbackStartVideoIdReference: Reference
|
||||||
|
|
||||||
|
val playbackStartDescriptorPatch = bytecodePatch(
|
||||||
|
description = "playbackStartDescriptorPatch"
|
||||||
|
) {
|
||||||
|
dependsOn(sharedResourceIdPatch)
|
||||||
|
|
||||||
|
execute {
|
||||||
|
// Find the obfuscated method name for PlaybackStartDescriptor.videoId()
|
||||||
|
playbackStartFeatureFlagFingerprint.methodOrThrow().apply {
|
||||||
|
val stringMethodIndex = indexOfFirstInstructionOrThrow {
|
||||||
|
val reference = getReference<MethodReference>()
|
||||||
|
reference?.definingClass == PLAYBACK_START_DESCRIPTOR_CLASS_DESCRIPTOR
|
||||||
|
&& reference.returnType == "Ljava/lang/String;"
|
||||||
|
}
|
||||||
|
|
||||||
|
playbackStartVideoIdReference = getInstruction<ReferenceInstruction>(stringMethodIndex).reference
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -373,13 +373,6 @@ If the layout of the player screen changes due to server-side changes, unintende
|
|||||||
<!-- PreferenceScreen: General -->
|
<!-- PreferenceScreen: General -->
|
||||||
<string name="revanced_preference_screen_general_title">General</string>
|
<string name="revanced_preference_screen_general_title">General</string>
|
||||||
|
|
||||||
<string name="revanced_change_layout_title">Change layout</string>
|
|
||||||
<string name="revanced_change_layout_entry_1">Original</string>
|
|
||||||
<string name="revanced_change_layout_entry_2">Phone</string>
|
|
||||||
<string name="revanced_change_layout_entry_3">Phone (Max 480 dp)</string>
|
|
||||||
<string name="revanced_change_layout_entry_4">Tablet</string>
|
|
||||||
<string name="revanced_change_layout_entry_5">Tablet (Min 600 dp)</string>
|
|
||||||
|
|
||||||
<string name="revanced_change_start_page_title">Change start page</string>
|
<string name="revanced_change_start_page_title">Change start page</string>
|
||||||
<string name="revanced_change_start_page_entry_default">Default</string>
|
<string name="revanced_change_start_page_entry_default">Default</string>
|
||||||
<string name="revanced_change_start_page_entry_browse">Browse channels</string>
|
<string name="revanced_change_start_page_entry_browse">Browse channels</string>
|
||||||
@ -438,6 +431,15 @@ Limitation: Back button on the toolbar may not work."</string>
|
|||||||
<string name="revanced_remove_viewer_discretion_dialog_summary">"Removes the viewer discretion dialog.
|
<string name="revanced_remove_viewer_discretion_dialog_summary">"Removes the viewer discretion dialog.
|
||||||
This does not bypass the age restriction. It just accepts it automatically."</string>
|
This does not bypass the age restriction. It just accepts it automatically."</string>
|
||||||
|
|
||||||
|
<string name="revanced_change_layout_title">Change layout</string>
|
||||||
|
<string name="revanced_change_layout_entry_1">Original</string>
|
||||||
|
<string name="revanced_change_layout_entry_2">Phone</string>
|
||||||
|
<string name="revanced_change_layout_entry_3">Phone (Max 480 dp)</string>
|
||||||
|
<string name="revanced_change_layout_entry_4">Tablet</string>
|
||||||
|
<string name="revanced_change_layout_entry_5">Tablet (Min 600 dp)</string>
|
||||||
|
<string name="revanced_open_channel_of_live_avatar_title">Open channel of live avatar</string>
|
||||||
|
<string name="revanced_open_channel_of_live_avatar_summary_on">Channel opens when the live avatar is clicked.</string>
|
||||||
|
<string name="revanced_open_channel_of_live_avatar_summary_off">Live stream opens when the live avatar is clicked.</string>
|
||||||
<string name="revanced_spoof_app_version_title">Spoof app version</string>
|
<string name="revanced_spoof_app_version_title">Spoof app version</string>
|
||||||
<string name="revanced_spoof_app_version_summary_on">Version spoofed</string>
|
<string name="revanced_spoof_app_version_summary_on">Version spoofed</string>
|
||||||
<string name="revanced_spoof_app_version_summary_off">Version not spoofed</string>
|
<string name="revanced_spoof_app_version_summary_off">Version not spoofed</string>
|
||||||
|
@ -304,6 +304,9 @@
|
|||||||
<!-- SETTINGS: LAYOUT_SWITCH
|
<!-- SETTINGS: LAYOUT_SWITCH
|
||||||
<ListPreference android:entries="@array/revanced_change_layout_entries" android:title="@string/revanced_change_layout_title" android:key="revanced_change_layout" android:entryValues="@array/revanced_change_layout_entry_values" />SETTINGS: LAYOUT_SWITCH -->
|
<ListPreference android:entries="@array/revanced_change_layout_entries" android:title="@string/revanced_change_layout_title" android:key="revanced_change_layout" android:entryValues="@array/revanced_change_layout_entry_values" />SETTINGS: LAYOUT_SWITCH -->
|
||||||
|
|
||||||
|
<!-- SETTINGS: OPEN_CHANNEL_OF_LIVE_AVATAR
|
||||||
|
<SwitchPreference android:title="@string/revanced_open_channel_of_live_avatar_title" android:key="revanced_open_channel_of_live_avatar" android:summaryOn="@string/revanced_open_channel_of_live_avatar_summary_on" android:summaryOff="@string/revanced_open_channel_of_live_avatar_summary_off" />SETTINGS: OPEN_CHANNEL_OF_LIVE_AVATAR -->
|
||||||
|
|
||||||
<!-- SETTINGS: SPOOF_APP_VERSION
|
<!-- SETTINGS: SPOOF_APP_VERSION
|
||||||
<SwitchPreference android:title="@string/revanced_spoof_app_version_title" android:key="revanced_spoof_app_version" android:summaryOn="@string/revanced_spoof_app_version_summary_on" android:summaryOff="@string/revanced_spoof_app_version_summary_off" />
|
<SwitchPreference android:title="@string/revanced_spoof_app_version_title" android:key="revanced_spoof_app_version" android:summaryOn="@string/revanced_spoof_app_version_summary_on" android:summaryOff="@string/revanced_spoof_app_version_summary_off" />
|
||||||
<app.revanced.extension.shared.settings.preference.WideListPreference android:title="@string/revanced_spoof_app_version_target_entry_title" android:key="revanced_spoof_app_version_target" android:entries="@array/revanced_spoof_app_version_target_entries" android:entryValues="@array/revanced_spoof_app_version_target_entry_values" />
|
<app.revanced.extension.shared.settings.preference.WideListPreference android:title="@string/revanced_spoof_app_version_target_entry_title" android:key="revanced_spoof_app_version_target" android:entries="@array/revanced_spoof_app_version_target_entries" android:entryValues="@array/revanced_spoof_app_version_target_entry_values" />
|
||||||
@ -885,6 +888,7 @@
|
|||||||
<Preference android:title="Layout switch" android:summary="@string/revanced_patches_excluded" android:selectable="false"/>
|
<Preference android:title="Layout switch" android:summary="@string/revanced_patches_excluded" android:selectable="false"/>
|
||||||
<Preference android:title="Miniplayer" android:summary="@string/revanced_patches_excluded" android:selectable="false"/>
|
<Preference android:title="Miniplayer" android:summary="@string/revanced_patches_excluded" android:selectable="false"/>
|
||||||
<Preference android:title="Navigation bar components" android:summary="@string/revanced_patches_excluded" android:selectable="false"/>
|
<Preference android:title="Navigation bar components" android:summary="@string/revanced_patches_excluded" android:selectable="false"/>
|
||||||
|
<Preference android:title="Open channel of live avatar" android:summary="@string/revanced_patches_excluded" android:selectable="false"/>
|
||||||
<Preference android:title="Remove viewer discretion dialog" android:summary="@string/revanced_patches_excluded" android:selectable="false"/>
|
<Preference android:title="Remove viewer discretion dialog" android:summary="@string/revanced_patches_excluded" android:selectable="false"/>
|
||||||
<Preference android:title="Spoof app version" android:summary="@string/revanced_patches_excluded" android:selectable="false"/>
|
<Preference android:title="Spoof app version" android:summary="@string/revanced_patches_excluded" android:selectable="false"/>
|
||||||
<Preference android:title="Toolbar components" android:summary="@string/revanced_patches_excluded" android:selectable="false"/>
|
<Preference android:title="Toolbar components" android:summary="@string/revanced_patches_excluded" android:selectable="false"/>
|
||||||
|
Reference in New Issue
Block a user