diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 74cf289a7..9eda7a1a5 100755 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -19,12 +19,12 @@ if (isFullBuild && System.getenv("PULL_REQUEST") == null) { android { namespace = "com.zionhuang.music" - compileSdk = 35 - buildToolsVersion = "35.0.0" + compileSdk = 34 + buildToolsVersion = "34.0.0" defaultConfig { applicationId = "com.zionhuang.music" minSdk = 24 - targetSdk = 35 + targetSdk = 34 versionCode = 26 versionName = "0.5.10" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" diff --git a/app/src/foss/java/com/zionhuang/music/utils/Utils.kt b/app/src/foss/java/com/zionhuang/music/utils/Utils.kt index c6cbfa29e..30a7da8ce 100644 --- a/app/src/foss/java/com/zionhuang/music/utils/Utils.kt +++ b/app/src/foss/java/com/zionhuang/music/utils/Utils.kt @@ -1,7 +1,6 @@ package com.zionhuang.music.utils import com.zionhuang.music.MainActivity -import java.lang.Exception fun reportException(throwable: Throwable) { throwable.printStackTrace() diff --git a/app/src/full/java/com/zionhuang/music/utils/TranslationHelper.kt b/app/src/full/java/com/zionhuang/music/utils/TranslationHelper.kt index 900107ec1..f9fad5d88 100644 --- a/app/src/full/java/com/zionhuang/music/utils/TranslationHelper.kt +++ b/app/src/full/java/com/zionhuang/music/utils/TranslationHelper.kt @@ -20,7 +20,8 @@ object TranslationHelper { suspend fun translate(lyrics: LyricsEntity): LyricsEntity { cache[lyrics.id]?.let { return it } - val isSynced = lyrics.lyrics.startsWith("[") + return try { + val isSynced = lyrics.lyrics.startsWith("[") val sourceLanguage = TranslateLanguage.fromLanguageTag( LanguageIdentification.getClient().identifyLanguage( lyrics.lyrics.lines().joinToString(separator = "\n") { it.replace("\\[\\d{2}:\\d{2}.\\d{2,3}\\] *".toRegex(), "") } @@ -63,9 +64,12 @@ object TranslationHelper { .joinToString(separator = "\n") } ) - }.also { cache.put(it.id, it) } + } catch (e: Exception) { + lyrics // Return original lyrics if translation fails + } + } } suspend fun clearModels() { diff --git a/innertube/src/main/java/com/zionhuang/innertube/InnerTube.kt b/innertube/src/main/java/com/zionhuang/innertube/InnerTube.kt index ae0775f1d..095de4d42 100644 --- a/innertube/src/main/java/com/zionhuang/innertube/InnerTube.kt +++ b/innertube/src/main/java/com/zionhuang/innertube/InnerTube.kt @@ -10,7 +10,7 @@ import com.zionhuang.innertube.utils.sha1 import io.ktor.client.* import io.ktor.client.engine.okhttp.* import io.ktor.client.plugins.* -import io.ktor.client.plugins.compression.* +import io.ktor.client.features.compression.* import io.ktor.client.plugins.contentnegotiation.* import io.ktor.client.request.* import io.ktor.http.* @@ -61,7 +61,7 @@ class InnerTube { }) } - install(ContentEncoding) { + install(ContentEncoding.Feature) { brotli(1.0F) gzip(0.9F) deflate(0.8F) diff --git a/innertube/src/main/java/com/zionhuang/innertube/YouTube.kt b/innertube/src/main/java/com/zionhuang/innertube/YouTube.kt index 62349e442..a7e9a0180 100644 --- a/innertube/src/main/java/com/zionhuang/innertube/YouTube.kt +++ b/innertube/src/main/java/com/zionhuang/innertube/YouTube.kt @@ -130,7 +130,7 @@ object YouTube { ?.distinctBy { it.id } ?.ifEmpty { null } ?: return@mapNotNull null ) - }!! + } ?: emptyList() ) } @@ -447,15 +447,17 @@ object YouTube { } val audioStreams = innerTube.pipedStreams(videoId).body().audioStreams safePlayerResponse.copy( - streamingData = safePlayerResponse.streamingData?.copy( - adaptiveFormats = safePlayerResponse.streamingData.adaptiveFormats.mapNotNull { adaptiveFormat -> - audioStreams.find { it.bitrate == adaptiveFormat.bitrate }?.let { - adaptiveFormat.copy( - url = it.url - ) + streamingData = safePlayerResponse.streamingData?.let { streamingData -> + streamingData.copy( + adaptiveFormats = streamingData.adaptiveFormats.mapNotNull { adaptiveFormat -> + audioStreams.find { it.bitrate == adaptiveFormat.bitrate }?.let { + adaptiveFormat.copy( + url = it.url + ) + } } - } - ) + ) + } ) } diff --git a/innertube/src/main/java/com/zionhuang/innertube/models/response/AccountMenuResponse.kt b/innertube/src/main/java/com/zionhuang/innertube/models/response/AccountMenuResponse.kt index 0f48b8448..ce1d54499 100644 --- a/innertube/src/main/java/com/zionhuang/innertube/models/response/AccountMenuResponse.kt +++ b/innertube/src/main/java/com/zionhuang/innertube/models/response/AccountMenuResponse.kt @@ -35,9 +35,9 @@ data class AccountMenuResponse( val channelHandle: Runs?, ) { fun toAccountInfo() = AccountInfo( - name = accountName.runs!!.first().text, - email = email?.runs?.first()?.text, - channelHandle = channelHandle?.runs?.first()?.text + name = accountName.runs?.firstOrNull()?.text ?: "", + email = email?.runs?.firstOrNull()?.text, + channelHandle = channelHandle?.runs?.firstOrNull()?.text ) } } diff --git a/innertube/src/main/java/com/zionhuang/innertube/models/response/GetQueueResponse.kt b/innertube/src/main/java/com/zionhuang/innertube/models/response/GetQueueResponse.kt index fd326a3c5..64ad55248 100644 --- a/innertube/src/main/java/com/zionhuang/innertube/models/response/GetQueueResponse.kt +++ b/innertube/src/main/java/com/zionhuang/innertube/models/response/GetQueueResponse.kt @@ -5,7 +5,7 @@ import kotlinx.serialization.Serializable @Serializable data class GetQueueResponse( - val queueDatas: List, + val queueData: List, ) { @Serializable data class QueueData( diff --git a/innertube/src/main/java/com/zionhuang/innertube/models/response/SearchResponse.kt b/innertube/src/main/java/com/zionhuang/innertube/models/response/SearchResponse.kt index 845a7ac7f..931782fa6 100644 --- a/innertube/src/main/java/com/zionhuang/innertube/models/response/SearchResponse.kt +++ b/innertube/src/main/java/com/zionhuang/innertube/models/response/SearchResponse.kt @@ -17,7 +17,7 @@ data class SearchResponse( @Serializable data class ContinuationContents( - val musicShelfContinuation: MusicShelfContinuation, + val musicShelfContinuation: MusicShelfContinuation?, ) { @Serializable data class MusicShelfContinuation( diff --git a/innertube/src/main/java/com/zionhuang/innertube/pages/AlbumPage.kt b/innertube/src/main/java/com/zionhuang/innertube/pages/AlbumPage.kt index fddfe3916..640699a90 100644 --- a/innertube/src/main/java/com/zionhuang/innertube/pages/AlbumPage.kt +++ b/innertube/src/main/java/com/zionhuang/innertube/pages/AlbumPage.kt @@ -27,10 +27,12 @@ data class AlbumPage( ) } ?: return null, album = renderer.flexColumns.getOrNull(2)?.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.firstOrNull()?.let { - Album( - name = it.text, - id = it.navigationEndpoint?.browseEndpoint?.browseId!! - ) + it.navigationEndpoint?.browseEndpoint?.browseId?.let { browseId -> + Album( + name = it.text, + id = browseId + ) + } } ?: return null, duration = renderer.fixedColumns?.firstOrNull() ?.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.firstOrNull() diff --git a/innertube/src/main/java/com/zionhuang/innertube/pages/ArtistItemsPage.kt b/innertube/src/main/java/com/zionhuang/innertube/pages/ArtistItemsPage.kt index 109ec5d04..b9278d23f 100644 --- a/innertube/src/main/java/com/zionhuang/innertube/pages/ArtistItemsPage.kt +++ b/innertube/src/main/java/com/zionhuang/innertube/pages/ArtistItemsPage.kt @@ -96,7 +96,7 @@ data class ArtistItemsPage( shuffleEndpoint = renderer.menu?.menuRenderer?.items?.find { it.menuNavigationItemRenderer?.icon?.iconType == "MUSIC_SHUFFLE" }?.menuNavigationItemRenderer?.navigationEndpoint?.watchPlaylistEndpoint ?: return null, - radioEndpoint = renderer.menu.menuRenderer.items.find { + radioEndpoint = renderer.menu?.menuRenderer?.items?.find { it.menuNavigationItemRenderer?.icon?.iconType == "MIX" }?.menuNavigationItemRenderer?.navigationEndpoint?.watchPlaylistEndpoint ?: return null ) diff --git a/innertube/src/main/java/com/zionhuang/innertube/pages/ExplorePage.kt b/innertube/src/main/java/com/zionhuang/innertube/pages/ExplorePage.kt index faee829de..999919b27 100644 --- a/innertube/src/main/java/com/zionhuang/innertube/pages/ExplorePage.kt +++ b/innertube/src/main/java/com/zionhuang/innertube/pages/ExplorePage.kt @@ -1,6 +1,7 @@ package com.zionhuang.innertube.pages import com.zionhuang.innertube.models.AlbumItem +import com.zionhuang.innertube.models.MoodAndGenres data class ExplorePage( val newReleaseAlbums: List, diff --git a/innertube/src/main/java/com/zionhuang/innertube/pages/HomePage.kt b/innertube/src/main/java/com/zionhuang/innertube/pages/HomePage.kt index 9cbe12150..0ece11df9 100644 --- a/innertube/src/main/java/com/zionhuang/innertube/pages/HomePage.kt +++ b/innertube/src/main/java/com/zionhuang/innertube/pages/HomePage.kt @@ -24,11 +24,12 @@ data class HomePage( ) { companion object { fun fromMusicCarouselShelfRenderer(renderer: MusicCarouselShelfRenderer): Section? { + val header = renderer.header?.musicCarouselShelfBasicHeaderRenderer ?: return null return Section( - title = renderer.header?.musicCarouselShelfBasicHeaderRenderer?.title?.runs?.firstOrNull()?.text ?: return null, - label = renderer.header.musicCarouselShelfBasicHeaderRenderer.strapline?.runs?.firstOrNull()?.text, - thumbnail = renderer.header.musicCarouselShelfBasicHeaderRenderer.thumbnail?.musicThumbnailRenderer?.getThumbnailUrl(), - endpoint = renderer.header.musicCarouselShelfBasicHeaderRenderer.moreContentButton?.buttonRenderer?.navigationEndpoint?.browseEndpoint, + title = header.title?.runs?.firstOrNull()?.text ?: return null, + label = header.strapline?.runs?.firstOrNull()?.text, + thumbnail = header.thumbnail?.musicThumbnailRenderer?.getThumbnailUrl(), + endpoint = header.moreContentButton?.buttonRenderer?.navigationEndpoint?.browseEndpoint, items = renderer.contents.mapNotNull { it.musicTwoRowItemRenderer }.mapNotNull { diff --git a/innertube/src/main/java/com/zionhuang/innertube/pages/MoodAndGenres.kt b/innertube/src/main/java/com/zionhuang/innertube/pages/MoodAndGenres.kt index ecaed16e4..c6c48d480 100644 --- a/innertube/src/main/java/com/zionhuang/innertube/pages/MoodAndGenres.kt +++ b/innertube/src/main/java/com/zionhuang/innertube/pages/MoodAndGenres.kt @@ -19,9 +19,9 @@ data class MoodAndGenres( fun fromSectionListRendererContent(content: SectionListRenderer.Content): MoodAndGenres? { return MoodAndGenres( title = content.gridRenderer?.header?.gridHeaderRenderer?.title?.runs?.firstOrNull()?.text ?: return null, - items = content.gridRenderer.items - .mapNotNull(GridRenderer.Item::musicNavigationButtonRenderer) - .mapNotNull(::fromMusicNavigationButtonRenderer) + items = content.gridRenderer?.items + ?.mapNotNull(GridRenderer.Item::musicNavigationButtonRenderer) + ?.mapNotNull(::fromMusicNavigationButtonRenderer) ?: return null ) } diff --git a/innertube/src/main/java/com/zionhuang/innertube/pages/NewReleaseAlbumPage.kt b/innertube/src/main/java/com/zionhuang/innertube/pages/NewReleaseAlbumPage.kt index a94da4de7..e51b034dd 100644 --- a/innertube/src/main/java/com/zionhuang/innertube/pages/NewReleaseAlbumPage.kt +++ b/innertube/src/main/java/com/zionhuang/innertube/pages/NewReleaseAlbumPage.kt @@ -21,7 +21,7 @@ object NewReleaseAlbumPage { id = it.navigationEndpoint?.browseEndpoint?.browseId ) } ?: return null, - year = renderer.subtitle.runs.lastOrNull()?.text?.toIntOrNull(), + year = renderer.subtitle?.runs?.lastOrNull()?.text?.toIntOrNull(), thumbnail = renderer.thumbnailRenderer.musicThumbnailRenderer?.getThumbnailUrl() ?: return null, explicit = renderer.subtitleBadges?.find { it.musicInlineBadgeRenderer?.icon?.iconType == "MUSIC_EXPLICIT_BADGE" diff --git a/innertube/src/main/java/com/zionhuang/innertube/pages/NextPage.kt b/innertube/src/main/java/com/zionhuang/innertube/pages/NextPage.kt index e82553ef2..94df6bbb2 100644 --- a/innertube/src/main/java/com/zionhuang/innertube/pages/NextPage.kt +++ b/innertube/src/main/java/com/zionhuang/innertube/pages/NextPage.kt @@ -41,7 +41,7 @@ object NextPage { ) }, duration = renderer.lengthText?.runs?.firstOrNull()?.text?.parseTime() ?: return null, - thumbnail = renderer.thumbnail.thumbnails.lastOrNull()?.url ?: return null, + thumbnail = renderer.thumbnail?.thumbnails?.lastOrNull()?.url ?: return null, explicit = renderer.badges?.find { it.musicInlineBadgeRenderer?.icon?.iconType == "MUSIC_EXPLICIT_BADGE" } != null diff --git a/innertube/src/main/java/com/zionhuang/innertube/pages/PlaylistPage.kt b/innertube/src/main/java/com/zionhuang/innertube/pages/PlaylistPage.kt index f746258e5..21769bd01 100644 --- a/innertube/src/main/java/com/zionhuang/innertube/pages/PlaylistPage.kt +++ b/innertube/src/main/java/com/zionhuang/innertube/pages/PlaylistPage.kt @@ -15,6 +15,11 @@ data class PlaylistPage( val continuation: String?, ) { companion object { + /** + * Converts a MusicResponsiveListItemRenderer to a SongItem. + * @param renderer The renderer to convert + * @return A SongItem if conversion is successful, null otherwise + */ fun fromMusicResponsiveListItemRenderer(renderer: MusicResponsiveListItemRenderer): SongItem? { return SongItem( id = renderer.playlistItemData?.videoId ?: return null, @@ -27,11 +32,13 @@ data class PlaylistPage( id = it.navigationEndpoint?.browseEndpoint?.browseId, ) }.orEmpty().ifEmpty { return null }, - album = renderer.flexColumns.getOrNull(2)?.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.firstOrNull()?.let { - Album( - name = it.text, - id = it.navigationEndpoint?.browseEndpoint?.browseId ?: return@let null - ) + album = renderer.flexColumns.getOrNull(2)?.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.firstOrNull()?.let { run -> + run.navigationEndpoint?.browseEndpoint?.browseId?.let { id -> + Album( + name = run.text, + id = id + ) + } }, duration = renderer.fixedColumns?.firstOrNull()?.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.firstOrNull()?.text?.parseTime(), thumbnail = renderer.thumbnail?.musicThumbnailRenderer?.getThumbnailUrl() ?: return null, diff --git a/innertube/src/main/java/com/zionhuang/innertube/pages/RelatedPage.kt b/innertube/src/main/java/com/zionhuang/innertube/pages/RelatedPage.kt index 44eead418..04942bc8c 100644 --- a/innertube/src/main/java/com/zionhuang/innertube/pages/RelatedPage.kt +++ b/innertube/src/main/java/com/zionhuang/innertube/pages/RelatedPage.kt @@ -77,7 +77,7 @@ data class RelatedPage( shuffleEndpoint = renderer.menu?.menuRenderer?.items?.find { it.menuNavigationItemRenderer?.icon?.iconType == "MUSIC_SHUFFLE" }?.menuNavigationItemRenderer?.navigationEndpoint?.watchPlaylistEndpoint ?: return null, - radioEndpoint = renderer.menu.menuRenderer.items.find { + radioEndpoint = renderer.menu?.menuRenderer?.items?.find { it.menuNavigationItemRenderer?.icon?.iconType == "MIX" }?.menuNavigationItemRenderer?.navigationEndpoint?.watchPlaylistEndpoint ?: return null ) @@ -89,7 +89,7 @@ data class RelatedPage( shuffleEndpoint = renderer.menu?.menuRenderer?.items?.find { it.menuNavigationItemRenderer?.icon?.iconType == "MUSIC_SHUFFLE" }?.menuNavigationItemRenderer?.navigationEndpoint?.watchPlaylistEndpoint ?: return null, - radioEndpoint = renderer.menu.menuRenderer.items.find { + radioEndpoint = renderer.menu?.menuRenderer?.items?.find { it.menuNavigationItemRenderer?.icon?.iconType == "MIX" }?.menuNavigationItemRenderer?.navigationEndpoint?.watchPlaylistEndpoint ?: return null, ) diff --git a/innertube/src/main/java/com/zionhuang/innertube/pages/SearchPage.kt b/innertube/src/main/java/com/zionhuang/innertube/pages/SearchPage.kt index dc9dbafef..4124e338e 100644 --- a/innertube/src/main/java/com/zionhuang/innertube/pages/SearchPage.kt +++ b/innertube/src/main/java/com/zionhuang/innertube/pages/SearchPage.kt @@ -56,8 +56,8 @@ object SearchPage { shuffleEndpoint = renderer.menu?.menuRenderer?.items ?.find { it.menuNavigationItemRenderer?.icon?.iconType == "MUSIC_SHUFFLE" } ?.menuNavigationItemRenderer?.navigationEndpoint?.watchPlaylistEndpoint ?: return null, - radioEndpoint = renderer.menu.menuRenderer.items - .find { it.menuNavigationItemRenderer?.icon?.iconType == "MIX" } + radioEndpoint = renderer.menu?.menuRenderer?.items + ?.find { it.menuNavigationItemRenderer?.icon?.iconType == "MIX" } ?.menuNavigationItemRenderer?.navigationEndpoint?.watchPlaylistEndpoint ?: return null ) } diff --git a/innertube/src/main/java/com/zionhuang/innertube/pages/SearchSummaryPage.kt b/innertube/src/main/java/com/zionhuang/innertube/pages/SearchSummaryPage.kt index 69386fee5..ed7896fe5 100644 --- a/innertube/src/main/java/com/zionhuang/innertube/pages/SearchSummaryPage.kt +++ b/innertube/src/main/java/com/zionhuang/innertube/pages/SearchSummaryPage.kt @@ -167,8 +167,8 @@ data class SearchSummaryPage( shuffleEndpoint = renderer.menu?.menuRenderer?.items ?.find { it.menuNavigationItemRenderer?.icon?.iconType == "MUSIC_SHUFFLE" } ?.menuNavigationItemRenderer?.navigationEndpoint?.watchPlaylistEndpoint ?: return null, - radioEndpoint = renderer.menu.menuRenderer.items - .find { it.menuNavigationItemRenderer?.icon?.iconType == "MIX" } + radioEndpoint = renderer.menu?.menuRenderer?.items + ?.find { it.menuNavigationItemRenderer?.icon?.iconType == "MIX" } ?.menuNavigationItemRenderer?.navigationEndpoint?.watchPlaylistEndpoint ?: return null ) } diff --git a/innertube/src/main/java/com/zionhuang/innertube/utils/Utils.kt b/innertube/src/main/java/com/zionhuang/innertube/utils/Utils.kt index 2e727f0e6..a6a70960f 100644 --- a/innertube/src/main/java/com/zionhuang/innertube/utils/Utils.kt +++ b/innertube/src/main/java/com/zionhuang/innertube/utils/Utils.kt @@ -29,8 +29,8 @@ fun parseCookieString(cookie: String): Map = cookie.split("; ") .filter { it.isNotEmpty() } .associate { - val (key, value) = it.split("=") - key to value + val parts = it.split("=", limit = 2) + if (parts.size == 2) parts[0] to parts[1] else parts[0] to "" } fun String.parseTime(): Int? {