fix(YouTube - Spoof streaming data): Enhanced bitrate not available on Android VR, Android TV

This commit is contained in:
inotia00 2025-01-06 21:18:04 +09:00
parent aeb6750063
commit 8663436d3d
5 changed files with 61 additions and 13 deletions

View File

@ -28,6 +28,8 @@ object AppClient {
else else
"19.29.1" "19.29.1"
private const val DEVICE_MAKE_IOS = "Apple"
private const val OS_NAME_IOS = "iOS"
/** /**
* The device machine id for the iPhone 15 Pro Max (iPhone16,2), * The device machine id for the iPhone 15 Pro Max (iPhone16,2),
* used to get HDR with AV1 hardware decoding. * used to get HDR with AV1 hardware decoding.
@ -95,6 +97,7 @@ object AppClient {
* See [this GitLab](https://dumps.tadiphone.dev/dumps/oculus/eureka) for more information. * See [this GitLab](https://dumps.tadiphone.dev/dumps/oculus/eureka) for more information.
*/ */
private const val DEVICE_MODEL_ANDROID_VR = "Quest 3" private const val DEVICE_MODEL_ANDROID_VR = "Quest 3"
private const val DEVICE_MAKE_ANDROID_VR = "Oculus"
private const val OS_VERSION_ANDROID_VR = "12" private const val OS_VERSION_ANDROID_VR = "12"
/** /**
@ -119,6 +122,7 @@ object AppClient {
* See [this GitLab](https://dumps.tadiphone.dev/dumps/google/kirkwood) for more information. * See [this GitLab](https://dumps.tadiphone.dev/dumps/google/kirkwood) for more information.
*/ */
private const val DEVICE_MODEL_ANDROID_UNPLUGGED = "Google TV Streamer" private const val DEVICE_MODEL_ANDROID_UNPLUGGED = "Google TV Streamer"
private const val DEVICE_MAKE_ANDROID_UNPLUGGED = "Google"
private const val OS_VERSION_ANDROID_UNPLUGGED = "14" private const val OS_VERSION_ANDROID_UNPLUGGED = "14"
private const val ANDROID_SDK_VERSION_ANDROID_UNPLUGGED = "34" private const val ANDROID_SDK_VERSION_ANDROID_UNPLUGGED = "34"
private val USER_AGENT_ANDROID_UNPLUGGED = androidUserAgent( private val USER_AGENT_ANDROID_UNPLUGGED = androidUserAgent(
@ -128,6 +132,20 @@ object AppClient {
) )
// ANDROID CREATOR
/**
* Video not playable: Livestream / HDR
* Note: Audio track is not available
*/
private const val PACKAGE_NAME_ANDROID_CREATOR = "com.google.android.apps.youtube.creator"
private const val CLIENT_VERSION_ANDROID_CREATOR = "23.47.101"
private val USER_AGENT_ANDROID_CREATOR = androidUserAgent(
PACKAGE_NAME_ANDROID_CREATOR,
CLIENT_VERSION_ANDROID_CREATOR
)
// ANDROID MUSIC // ANDROID MUSIC
/** /**
* Video not playable: All videos that can't be played on YouTube Music * Video not playable: All videos that can't be played on YouTube Music
@ -139,7 +157,6 @@ object AppClient {
* It is not the default client yet, as it requires sufficient testing. * It is not the default client yet, as it requires sufficient testing.
*/ */
private const val CLIENT_VERSION_ANDROID_MUSIC = "4.27.53" private const val CLIENT_VERSION_ANDROID_MUSIC = "4.27.53"
private val ANDROID_SDK_VERSION_ANDROID_MUSIC = Build.VERSION.SDK_INT.toString()
private val USER_AGENT_ANDROID_MUSIC = androidUserAgent( private val USER_AGENT_ANDROID_MUSIC = androidUserAgent(
PACKAGE_NAME_ANDROID_MUSIC, PACKAGE_NAME_ANDROID_MUSIC,
CLIENT_VERSION_ANDROID_MUSIC CLIENT_VERSION_ANDROID_MUSIC
@ -195,15 +212,24 @@ object AppClient {
} }
} }
@Suppress("DEPRECATION")
enum class ClientType( enum class ClientType(
/** /**
* [YouTube client type](https://github.com/zerodytrash/YouTube-Internal-Clients?tab=readme-ov-file#clients) * [YouTube client type](https://github.com/zerodytrash/YouTube-Internal-Clients?tab=readme-ov-file#clients)
*/ */
val id: Int, val id: Int,
/**
* Device model, equivalent to [Build.MANUFACTURER] (System property: ro.product.vendor.manufacturer)
*/
val deviceMake: String = Build.MANUFACTURER,
/** /**
* Device model, equivalent to [Build.MODEL] (System property: ro.product.model) * Device model, equivalent to [Build.MODEL] (System property: ro.product.model)
*/ */
val deviceModel: String = Build.MODEL, val deviceModel: String = Build.MODEL,
/**
* Device OS name.
*/
val osName: String = "Android",
/** /**
* Device OS version, equivalent to [Build.VERSION.RELEASE] (System property: ro.system.build.version.release) * Device OS version, equivalent to [Build.VERSION.RELEASE] (System property: ro.system.build.version.release)
*/ */
@ -216,7 +242,7 @@ object AppClient {
* Android SDK version, equivalent to [Build.VERSION.SDK] (System property: ro.build.version.sdk) * Android SDK version, equivalent to [Build.VERSION.SDK] (System property: ro.build.version.sdk)
* Field is null if not applicable. * Field is null if not applicable.
*/ */
val androidSdkVersion: String? = null, val androidSdkVersion: String = Build.VERSION.SDK,
/** /**
* App version. * App version.
*/ */
@ -242,6 +268,7 @@ object AppClient {
) { ) {
ANDROID_VR( ANDROID_VR(
id = 28, id = 28,
deviceMake = DEVICE_MAKE_ANDROID_VR,
deviceModel = DEVICE_MODEL_ANDROID_VR, deviceModel = DEVICE_MODEL_ANDROID_VR,
osVersion = OS_VERSION_ANDROID_VR, osVersion = OS_VERSION_ANDROID_VR,
userAgent = USER_AGENT_ANDROID_VR, userAgent = USER_AGENT_ANDROID_VR,
@ -251,6 +278,7 @@ object AppClient {
), ),
ANDROID_UNPLUGGED( ANDROID_UNPLUGGED(
id = 29, id = 29,
deviceMake = DEVICE_MAKE_ANDROID_UNPLUGGED,
deviceModel = DEVICE_MODEL_ANDROID_UNPLUGGED, deviceModel = DEVICE_MODEL_ANDROID_UNPLUGGED,
osVersion = OS_VERSION_ANDROID_UNPLUGGED, osVersion = OS_VERSION_ANDROID_UNPLUGGED,
userAgent = USER_AGENT_ANDROID_UNPLUGGED, userAgent = USER_AGENT_ANDROID_UNPLUGGED,
@ -259,9 +287,18 @@ object AppClient {
requireAuth = true, requireAuth = true,
friendlyName = "Android TV" friendlyName = "Android TV"
), ),
ANDROID_CREATOR(
id = 14,
userAgent = USER_AGENT_ANDROID_CREATOR,
clientVersion = CLIENT_VERSION_ANDROID_CREATOR,
requireAuth = true,
friendlyName = "Android Studio"
),
IOS_UNPLUGGED( IOS_UNPLUGGED(
id = 33, id = 33,
deviceMake = DEVICE_MAKE_IOS,
deviceModel = DEVICE_MODEL_IOS, deviceModel = DEVICE_MODEL_IOS,
osName = OS_NAME_IOS,
osVersion = OS_VERSION_IOS, osVersion = OS_VERSION_IOS,
userAgent = USER_AGENT_IOS_UNPLUGGED, userAgent = USER_AGENT_IOS_UNPLUGGED,
clientVersion = CLIENT_VERSION_IOS_UNPLUGGED, clientVersion = CLIENT_VERSION_IOS_UNPLUGGED,
@ -273,7 +310,9 @@ object AppClient {
), ),
IOS( IOS(
id = 5, id = 5,
deviceMake = DEVICE_MAKE_IOS,
deviceModel = DEVICE_MODEL_IOS, deviceModel = DEVICE_MODEL_IOS,
osName = OS_NAME_IOS,
osVersion = OS_VERSION_IOS, osVersion = OS_VERSION_IOS,
userAgent = USER_AGENT_IOS, userAgent = USER_AGENT_IOS,
clientVersion = CLIENT_VERSION_IOS, clientVersion = CLIENT_VERSION_IOS,
@ -287,7 +326,6 @@ object AppClient {
ANDROID_MUSIC( ANDROID_MUSIC(
id = 21, id = 21,
userAgent = USER_AGENT_ANDROID_MUSIC, userAgent = USER_AGENT_ANDROID_MUSIC,
androidSdkVersion = ANDROID_SDK_VERSION_ANDROID_MUSIC,
clientVersion = CLIENT_VERSION_ANDROID_MUSIC, clientVersion = CLIENT_VERSION_ANDROID_MUSIC,
requireAuth = true, requireAuth = true,
friendlyName = "Android Music" friendlyName = "Android Music"
@ -300,6 +338,7 @@ object AppClient {
ANDROID_VR, ANDROID_VR,
ANDROID_UNPLUGGED, ANDROID_UNPLUGGED,
IOS_UNPLUGGED, IOS_UNPLUGGED,
ANDROID_CREATOR,
IOS, IOS,
) )

View File

@ -207,7 +207,7 @@ public class SpoofStreamingDataPatch {
String id = uri.getQueryParameter("id"); String id = uri.getQueryParameter("id");
if (id == null) { if (id == null) {
Logger.printException(() -> "Ignoring request with no id. Url: " + url); Logger.printException(() -> "Ignoring request with no id: " + url);
return; return;
} }

View File

@ -62,14 +62,12 @@ object PlayerRoutes {
val client = JSONObject() val client = JSONObject()
client.put("clientName", clientType.clientName) client.put("clientName", clientType.clientName)
client.put("clientVersion", clientType.clientVersion) client.put("clientVersion", clientType.clientVersion)
client.put("deviceMake", clientType.deviceMake)
client.put("deviceModel", clientType.deviceModel) client.put("deviceModel", clientType.deviceModel)
client.put("osName", clientType.osName)
client.put("osVersion", clientType.osVersion) client.put("osVersion", clientType.osVersion)
if (clientType.androidSdkVersion != null) { if (clientType.osName == "Android") {
client.put("androidSdkVersion", clientType.androidSdkVersion) client.put("androidSdkVersion", clientType.androidSdkVersion)
client.put("osName", "Android")
} else {
client.put("deviceMake", "Apple")
client.put("osName", "iOS")
} }
if (!clientType.supportsCookies) { if (!clientType.supportsCookies) {
client.put("hl", LOCALE_LANGUAGE) client.put("hl", LOCALE_LANGUAGE)
@ -130,12 +128,22 @@ object PlayerRoutes {
} }
@JvmStatic @JvmStatic
fun getPlayerResponseConnectionFromRoute(route: CompiledRoute, clientType: AppClient.ClientType): HttpURLConnection {
return getPlayerResponseConnectionFromRoute(route, clientType.userAgent, clientType.id.toString())
}
@JvmStatic
fun getPlayerResponseConnectionFromRoute(route: CompiledRoute, clientType: WebClient.ClientType): HttpURLConnection {
return getPlayerResponseConnectionFromRoute(route, clientType.userAgent, clientType.id.toString())
}
@Throws(IOException::class) @Throws(IOException::class)
fun getPlayerResponseConnectionFromRoute(route: CompiledRoute, userAgent: String): HttpURLConnection { fun getPlayerResponseConnectionFromRoute(route: CompiledRoute, userAgent: String, clientVersion: String): HttpURLConnection {
val connection = Requester.getConnectionFromCompiledRoute(YT_API_URL, route) val connection = Requester.getConnectionFromCompiledRoute(YT_API_URL, route)
connection.setRequestProperty("Content-Type", "application/json") connection.setRequestProperty("Content-Type", "application/json")
connection.setRequestProperty("User-Agent", userAgent) connection.setRequestProperty("User-Agent", userAgent)
connection.setRequestProperty("X-YouTube-Client-Version", clientVersion)
connection.useCaches = false connection.useCaches = false
connection.doOutput = true connection.doOutput = true
@ -144,4 +152,5 @@ object PlayerRoutes {
connection.readTimeout = CONNECTION_TIMEOUT_MILLISECONDS connection.readTimeout = CONNECTION_TIMEOUT_MILLISECONDS
return connection return connection
} }
} }

View File

@ -171,7 +171,7 @@ class StreamingDataRequest private constructor(
Logger.printDebug { "Fetching video streams for: $videoId using client: $clientType" } Logger.printDebug { "Fetching video streams for: $videoId using client: $clientType" }
try { try {
val connection = getPlayerResponseConnectionFromRoute(GET_STREAMING_DATA, clientType.userAgent) val connection = getPlayerResponseConnectionFromRoute(GET_STREAMING_DATA, clientType)
connection.connectTimeout = HTTP_TIMEOUT_MILLISECONDS connection.connectTimeout = HTTP_TIMEOUT_MILLISECONDS
connection.readTimeout = HTTP_TIMEOUT_MILLISECONDS connection.readTimeout = HTTP_TIMEOUT_MILLISECONDS

View File

@ -121,7 +121,7 @@ class MusicRequest private constructor(private val videoId: String, private val
Logger.printDebug { "Fetching playlist request for: $videoId using client: $clientTypeName" } Logger.printDebug { "Fetching playlist request for: $videoId using client: $clientTypeName" }
try { try {
val connection = PlayerRoutes.getPlayerResponseConnectionFromRoute(PlayerRoutes.GET_PLAYLIST_PAGE, clientType.userAgent) val connection = PlayerRoutes.getPlayerResponseConnectionFromRoute(PlayerRoutes.GET_PLAYLIST_PAGE, clientType)
val requestBody = val requestBody =
PlayerRoutes.createApplicationRequestBody(clientType, videoId, "RD$videoId") PlayerRoutes.createApplicationRequestBody(clientType, videoId, "RD$videoId")
@ -158,7 +158,7 @@ class MusicRequest private constructor(private val videoId: String, private val
Logger.printDebug { "Fetching playability request for: $videoId using client: $clientTypeName" } Logger.printDebug { "Fetching playability request for: $videoId using client: $clientTypeName" }
try { try {
val connection = PlayerRoutes.getPlayerResponseConnectionFromRoute(PlayerRoutes.GET_CATEGORY, clientType.userAgent) val connection = PlayerRoutes.getPlayerResponseConnectionFromRoute(PlayerRoutes.GET_CATEGORY, clientType)
val requestBody = val requestBody =
PlayerRoutes.createWebInnertubeBody(clientType, videoId) PlayerRoutes.createWebInnertubeBody(clientType, videoId)