Skip to content

Commit

Permalink
Fix playback issues caused by new restrictions on YouTube Music
Browse files Browse the repository at this point in the history
Please be aware that this fix is experimental, but since this is the only thing that is working right now, I commit to master anyways.
- Change player client versions
- Add fallback mechanism
- Add non-music endpoints
- Add iOS client
- Add new parameters/headers in order for YouTube to accept the requests
- Add JavaScript challenge solver (WIP, doesn't really work correctly as of right now)
- Add more logging for a nicer debugging experience (Logback, etc.)

Other nice-to-haves:
- Add reload button to Stats for nerds
  • Loading branch information
25huizengek1 committed Oct 25, 2024
1 parent a82180a commit 504b6f7
Show file tree
Hide file tree
Showing 18 changed files with 458 additions and 133 deletions.
4 changes: 4 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,10 @@ dependencies {
implementation(libs.room)
ksp(libs.room.compiler)

implementation(libs.log4j)
implementation(libs.slf4j)
implementation(libs.logback)

implementation(projects.providers.github)
implementation(projects.providers.innertube)
implementation(projects.providers.kugou)
Expand Down
8 changes: 7 additions & 1 deletion app/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,10 @@
-dontwarn org.openjsse.javax.net.ssl.SSLParameters
-dontwarn org.openjsse.javax.net.ssl.SSLSocket
-dontwarn org.openjsse.net.ssl.OpenJSSE
-dontwarn org.slf4j.impl.StaticLoggerBinder
-dontwarn org.slf4j.impl.StaticLoggerBinder

# Rhino
-keep class org.mozilla.javascript.** { *; }
-keep class org.mozilla.classfile.ClassFileWriter
-dontwarn org.mozilla.javascript.JavaToJSONConverters
-dontwarn org.mozilla.javascript.tools.**
18 changes: 18 additions & 0 deletions app/src/main/assets/logback.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<configuration
xmlns="https://tony19.github.io/logback-android/xml"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://tony19.github.io/logback-android/xml https://cdn.jsdelivr.net/gh/tony19/logback-android/logback.xsd"
>
<appender name="logcat" class="ch.qos.logback.classic.android.LogcatAppender">
<tagEncoder>
<pattern>%logger{12}</pattern>
</tagEncoder>
<encoder>
<pattern>[%-20thread] %msg</pattern>
</encoder>
</appender>

<root level="DEBUG">
<appender-ref ref="logcat" />
</root>
</configuration>
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,32 @@ import androidx.annotation.OptIn
import androidx.media3.common.PlaybackException
import androidx.media3.common.util.UnstableApi

class PlayableFormatNotFoundException : PlaybackException(
class PlayableFormatNotFoundException(cause: Throwable? = null) : PlaybackException(
/* message = */ "Playable format not found",
/* cause = */ null,
/* cause = */ cause,
/* errorCode = */ ERROR_CODE_IO_FILE_NOT_FOUND
)

class UnplayableException : PlaybackException(
class UnplayableException(cause: Throwable? = null) : PlaybackException(
/* message = */ "Unplayable",
/* cause = */ null,
/* cause = */ cause,
/* errorCode = */ ERROR_CODE_IO_UNSPECIFIED
)

class LoginRequiredException : PlaybackException(
class LoginRequiredException(cause: Throwable? = null) : PlaybackException(
/* message = */ "Login required",
/* cause = */ null,
/* cause = */ cause,
/* errorCode = */ ERROR_CODE_AUTHENTICATION_EXPIRED
)

class VideoIdMismatchException : PlaybackException(
class VideoIdMismatchException(cause: Throwable? = null) : PlaybackException(
/* message = */ "Requested video ID doesn't match returned video ID",
/* cause = */ null,
/* cause = */ cause,
/* errorCode = */ ERROR_CODE_IO_UNSPECIFIED
)

class RestrictedVideoException(cause: Throwable? = null) : PlaybackException(
/* message = */ "This video is restricted",
/* cause = */ cause,
/* errorCode = */ ERROR_CODE_PARENTAL_CONTROL_RESTRICTED
)
19 changes: 15 additions & 4 deletions app/src/main/kotlin/app/vitune/android/service/PlayerService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -500,8 +500,7 @@ class PlayerService : InvincibleService(), Player.Listener, PlaybackStatsListene
super.onPlayerError(error)

if (
error.findCause<InvalidResponseCodeException>()?.responseCode == 416 ||
error.findCause<VideoIdMismatchException>() != null
error.findCause<InvalidResponseCodeException>()?.responseCode == 416
) {
player.pause()
player.prepare()
Expand Down Expand Up @@ -1389,7 +1388,13 @@ class PlayerService : InvincibleService(), Player.Listener, PlaybackStatsListene
}
}

format.url
runCatching {
runBlocking(Dispatchers.IO) {
format.findUrl()
}
}.getOrElse {
throw RestrictedVideoException(it)
}
}

"UNPLAYABLE" -> throw UnplayableException()
Expand All @@ -1402,7 +1407,13 @@ class PlayerService : InvincibleService(), Player.Listener, PlaybackStatsListene
)
} ?: throw UnplayableException()

val uri = url.toUri()
val uri = url.toUri().let {
if (body.cpn == null) it
else it
.buildUpon()
.appendQueryParameter("cpn", body.cpn)
.build()
}

uriCache.push(
key = mediaId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import androidx.compose.foundation.text.BasicText
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import app.vitune.android.utils.center
import app.vitune.android.utils.disabled
import app.vitune.android.utils.medium
import app.vitune.core.ui.LocalAppearance
import app.vitune.core.ui.primaryButton
Expand All @@ -26,7 +27,7 @@ fun SecondaryTextButton(

BasicText(
text = text,
style = typography.xxs.medium.copy(textAlign = TextAlign.Center),
style = typography.xxs.medium.center.let { if (enabled) it else it.disabled },
modifier = modifier
.clip(16.dp.roundedShape)
.clickable(enabled = enabled, onClick = onClick)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import androidx.compose.animation.fadeOut
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
Expand All @@ -21,6 +20,8 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableLongStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
Expand All @@ -35,6 +36,8 @@ import app.vitune.android.Database
import app.vitune.android.LocalPlayerServiceBinder
import app.vitune.android.R
import app.vitune.android.models.Format
import app.vitune.android.service.PlayerService
import app.vitune.android.ui.components.themed.SecondaryTextButton
import app.vitune.android.utils.color
import app.vitune.android.utils.medium
import app.vitune.core.ui.LocalAppearance
Expand All @@ -47,6 +50,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlin.math.roundToInt

Expand All @@ -66,12 +70,45 @@ fun StatsForNerds(
val context = LocalContext.current
val binder = LocalPlayerServiceBinder.current

val coroutineScope = rememberCoroutineScope()

var cachedBytes by remember(binder, mediaId) {
mutableLongStateOf(binder?.cache?.getCachedBytes(mediaId, 0, -1) ?: 0L)
}

var format by remember { mutableStateOf<Format?>(null) }

var hasReloaded by rememberSaveable { mutableStateOf(false) }

suspend fun reload(binder: PlayerService.Binder) {
binder.player.currentMediaItem
?.takeIf { it.mediaId == mediaId }
?.let { mediaItem ->
withContext(Dispatchers.IO) {
delay(2000)

Innertube
.player(PlayerBody(videoId = mediaId))
?.onSuccess { response ->
response?.streamingData?.highestQualityFormat?.let { format ->
Database.insert(mediaItem)
Database.insert(
Format(
songId = mediaId,
itag = format.itag,
mimeType = format.mimeType,
bitrate = format.bitrate,
loudnessDb = response.playerConfig?.audioConfig?.normalizedLoudnessDb,
contentLength = format.contentLength,
lastModified = format.lastModified
)
)
}
}
}
}
}

LaunchedEffect(binder, mediaId) {
val currentBinder = binder ?: return@LaunchedEffect

Expand All @@ -80,32 +117,7 @@ fun StatsForNerds(
.distinctUntilChanged()
.collectLatest { currentFormat ->
if (currentFormat?.itag != null) format = currentFormat
else currentBinder.player.currentMediaItem
?.takeIf { it.mediaId == mediaId }
?.let { mediaItem ->
withContext(Dispatchers.IO) {
delay(2000)

Innertube
.player(PlayerBody(videoId = mediaId))
?.onSuccess { response ->
response.streamingData?.highestQualityFormat?.let { format ->
Database.insert(mediaItem)
Database.insert(
Format(
songId = mediaId,
itag = format.itag,
mimeType = format.mimeType,
bitrate = format.bitrate,
loudnessDb = response.playerConfig?.audioConfig?.normalizedLoudnessDb,
contentLength = format.contentLength,
lastModified = format.lastModified
)
)
}
}
}
}
else reload(currentBinder)
}
}

Expand All @@ -131,19 +143,19 @@ fun StatsForNerds(
}
}

Box(
Column(
modifier = modifier
.pointerInput(Unit) {
detectTapGestures(onTap = { onDismiss() })
}
.background(colorPalette.overlay)
.fillMaxSize()
.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Row(
horizontalArrangement = Arrangement.spacedBy(16.dp),
modifier = Modifier
.align(Alignment.Center)
.padding(all = 16.dp)
modifier = Modifier.padding(all = 16.dp)
) {
@Composable
fun Text(text: String) = BasicText(
Expand Down Expand Up @@ -195,5 +207,19 @@ fun StatsForNerds(
)
}
}

binder?.let {
SecondaryTextButton(
text = stringResource(R.string.reload),
onClick = {
hasReloaded = true

coroutineScope.launch {
reload(it)
}
},
enabled = !hasReloaded
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import app.vitune.android.R
import app.vitune.android.preferences.PlayerPreferences
import app.vitune.android.service.LoginRequiredException
import app.vitune.android.service.PlayableFormatNotFoundException
import app.vitune.android.service.RestrictedVideoException
import app.vitune.android.service.UnplayableException
import app.vitune.android.service.VideoIdMismatchException
import app.vitune.android.service.isLocal
Expand Down Expand Up @@ -247,7 +248,9 @@ fun Thumbnail(

is PlayableFormatNotFoundException -> stringResource(R.string.error_unplayable)
is UnplayableException -> stringResource(R.string.error_source_deleted)
is LoginRequiredException -> stringResource(R.string.error_server_restrictions)
is LoginRequiredException, is RestrictedVideoException ->
stringResource(R.string.error_server_restrictions)

is VideoIdMismatchException -> stringResource(R.string.error_id_mismatch)
else -> stringResource(R.string.error_unknown_playback)
}
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values-nl/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@
<string name="size">Grootte</string>
<string name="cached">Gecachet</string>
<string name="loudness">Luidheid</string>
<string name="reload">Herlaad</string>

<string name="view_album">Toon album</string>
<string name="view_playlist">Toon afspeellijst</string>
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@
<string name="size">Size</string>
<string name="cached">Cached</string>
<string name="loudness">Loudness</string>
<string name="reload">Reload</string>

<string name="view_album">View album</string>
<string name="view_playlist">View playlist</string>
Expand Down
6 changes: 6 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,16 @@ ktor_client_core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
ktor_client_cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" }
ktor_client_okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
ktor_client_content_negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
ktor_client_logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" }
ktor_client_encoding = { module = "io.ktor:ktor-client-encoding", version.ref = "ktor" }
ktor_client_serialization = { module = "io.ktor:ktor-client-serialization", version.ref = "ktor" }
ktor_serialization_json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }

rhino = { module = "org.mozilla:rhino", version = "1.7.15" }
log4j = { module = "org.apache.logging.log4j:log4j-api", version = "2.3" }
slf4j = { module = "org.slf4j:slf4j-api", version = "2.0.16" }
logback = { module = "com.github.tony19:logback-android", version = "3.0.0" }

brotli = { module = "org.brotli:dec", version = "0.1.2" }
palette = { module = "androidx.palette:palette", version = "1.0.0" }
monet = { module = "com.github.KieronQuinn:MonetCompat", version = "0.4.1" }
Expand Down
4 changes: 4 additions & 0 deletions providers/innertube/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,14 @@ dependencies {
implementation(libs.ktor.client.core)
implementation(libs.ktor.client.okhttp)
implementation(libs.ktor.client.content.negotiation)
implementation(libs.ktor.client.logging)
implementation(libs.ktor.client.encoding)
implementation(libs.ktor.client.serialization)
implementation(libs.ktor.serialization.json)

implementation(libs.rhino)
implementation(libs.log4j)

detektPlugins(libs.detekt.compose)
detektPlugins(libs.detekt.formatting)
}
Expand Down
Loading

0 comments on commit 504b6f7

Please sign in to comment.