├── .gitattributes ├── .github └── workflows │ └── android.yml ├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── CMakeLists.txt ├── agconnect-services.json ├── build.gradle.kts ├── foss │ └── release │ │ └── output-metadata.json ├── full │ └── release │ │ ├── app-full-release.aab │ │ └── output-metadata.json ├── google-services.json ├── proguard-rules.pro ├── schemas │ └── com.zionhuang.music.db.InternalDatabase │ │ ├── 1.json │ │ ├── 10.json │ │ ├── 11.json │ │ ├── 12.json │ │ ├── 2.json │ │ ├── 3.json │ │ ├── 4.json │ │ ├── 5.json │ │ ├── 6.json │ │ ├── 7.json │ │ ├── 8.json │ │ └── 9.json └── src │ ├── debug │ └── res │ │ ├── values │ │ └── app_name.xml │ │ └── xml-v25 │ │ └── shortcuts.xml │ ├── foss │ └── java │ │ └── com │ │ └── zionhuang │ │ └── music │ │ └── utils │ │ ├── TranslationHelper.kt │ │ └── Utils.kt │ ├── full │ └── java │ │ └── com │ │ └── zionhuang │ │ └── music │ │ └── utils │ │ ├── TranslationHelper.kt │ │ └── Utils.kt │ └── main │ ├── AndroidManifest.xml │ ├── ic_launcher-playstore.png │ ├── java │ └── com │ │ └── zionhuang │ │ └── music │ │ ├── App.kt │ │ ├── MainActivity.kt │ │ ├── SplashActivity.kt │ │ ├── constants │ │ ├── Constants.kt │ │ ├── Dimensions.kt │ │ ├── MediaSessionConstants.kt │ │ ├── PreferenceKeys.kt │ │ └── StatPeriod.kt │ │ ├── db │ │ ├── Converters.kt │ │ ├── DatabaseDao.kt │ │ ├── MusicDatabase.kt │ │ └── entities │ │ │ ├── Album.kt │ │ │ ├── AlbumArtistMap.kt │ │ │ ├── AlbumEntity.kt │ │ │ ├── AlbumWithSongs.kt │ │ │ ├── Artist.kt │ │ │ ├── ArtistEntity.kt │ │ │ ├── Event.kt │ │ │ ├── EventWithSong.kt │ │ │ ├── FormatEntity.kt │ │ │ ├── LocalItem.kt │ │ │ ├── LyricsEntity.kt │ │ │ ├── Playlist.kt │ │ │ ├── PlaylistEntity.kt │ │ │ ├── PlaylistSong.kt │ │ │ ├── PlaylistSongMap.kt │ │ │ ├── PlaylistSongMapPreview.kt │ │ │ ├── RelatedSongMap.kt │ │ │ ├── SearchHistory.kt │ │ │ ├── Song.kt │ │ │ ├── SongAlbumMap.kt │ │ │ ├── SongArtistMap.kt │ │ │ ├── SongEntity.kt │ │ │ ├── SortedSongAlbumMap.kt │ │ │ └── SortedSongArtistMap.kt │ │ ├── di │ │ └── AppModule.kt │ │ ├── extensions │ │ ├── CoroutineExt.kt │ │ ├── FileExt.kt │ │ ├── ListExt.kt │ │ ├── MediaItemExt.kt │ │ ├── PlayerExt.kt │ │ ├── StringExt.kt │ │ └── UtilExt.kt │ │ ├── lyrics │ │ ├── KuGouLyricsProvider.kt │ │ ├── LyricsEntry.kt │ │ ├── LyricsHelper.kt │ │ ├── LyricsProvider.kt │ │ ├── LyricsUtils.kt │ │ ├── YouTubeLyricsProvider.kt │ │ └── YouTubeSubtitleLyricsProvider.kt │ │ ├── models │ │ ├── ItemsPage.kt │ │ ├── MediaMetadata.kt │ │ └── PersistQueue.kt │ │ ├── playback │ │ ├── DownloadUtil.kt │ │ ├── ExoDownloadService.kt │ │ ├── MediaLibrarySessionCallback.kt │ │ ├── MusicService.kt │ │ ├── PlayerConnection.kt │ │ ├── SleepTimer.kt │ │ └── queues │ │ │ ├── EmptyQueue.kt │ │ │ ├── ListQueue.kt │ │ │ ├── Queue.kt │ │ │ ├── YouTubeAlbumRadio.kt │ │ │ └── YouTubeQueue.kt │ │ ├── ui │ │ ├── component │ │ │ ├── AutoResizeText.kt │ │ │ ├── BigSeekBar.kt │ │ │ ├── BottomSheet.kt │ │ │ ├── BottomSheetMenu.kt │ │ │ ├── ChipsRow.kt │ │ │ ├── Dialog.kt │ │ │ ├── EmptyPlaceholder.kt │ │ │ ├── GridMenu.kt │ │ │ ├── HideOnScrollFAB.kt │ │ │ ├── IconButton.kt │ │ │ ├── Items.kt │ │ │ ├── Lyrics.kt │ │ │ ├── NavigationTile.kt │ │ │ ├── NavigationTitle.kt │ │ │ ├── PlayingIndicator.kt │ │ │ ├── Preference.kt │ │ │ ├── SearchBar.kt │ │ │ ├── SortHeader.kt │ │ │ └── shimmer │ │ │ │ ├── ButtonPlaceholder.kt │ │ │ │ ├── GridItemPlaceholder.kt │ │ │ │ ├── ListItemPlaceholder.kt │ │ │ │ ├── ShimmerHost.kt │ │ │ │ └── TextPlaceholder.kt │ │ ├── menu │ │ │ ├── AddToPlaylistDialog.kt │ │ │ ├── AlbumMenu.kt │ │ │ ├── ArtistMenu.kt │ │ │ ├── LyricsMenu.kt │ │ │ ├── PlayerMenu.kt │ │ │ ├── PlaylistMenu.kt │ │ │ ├── SongMenu.kt │ │ │ ├── YouTubeAlbumMenu.kt │ │ │ ├── YouTubeArtistMenu.kt │ │ │ ├── YouTubePlaylistMenu.kt │ │ │ └── YouTubeSongMenu.kt │ │ ├── player │ │ │ ├── MiniPlayer.kt │ │ │ ├── PlaybackError.kt │ │ │ ├── Player.kt │ │ │ ├── Queue.kt │ │ │ └── Thumbnail.kt │ │ ├── screens │ │ │ ├── AccountScreen.kt │ │ │ ├── AlbumScreen.kt │ │ │ ├── HistoryScreen.kt │ │ │ ├── HomeScreen.kt │ │ │ ├── LoginScreen.kt │ │ │ ├── MoodAndGenresScreen.kt │ │ │ ├── NewReleaseScreen.kt │ │ │ ├── Screens.kt │ │ │ ├── StatsScreen.kt │ │ │ ├── YouTubeBrowseScreen.kt │ │ │ ├── artist │ │ │ │ ├── ArtistItemsScreen.kt │ │ │ │ ├── ArtistScreen.kt │ │ │ │ └── ArtistSongsScreen.kt │ │ │ ├── library │ │ │ │ ├── LibraryAlbumsScreen.kt │ │ │ │ ├── LibraryArtistsScreen.kt │ │ │ │ ├── LibraryPlaylistsScreen.kt │ │ │ │ └── LibrarySongsScreen.kt │ │ │ ├── playlist │ │ │ │ ├── LocalPlaylistScreen.kt │ │ │ │ └── OnlinePlaylistScreen.kt │ │ │ ├── search │ │ │ │ ├── LocalSearchScreen.kt │ │ │ │ ├── OnlineSearchResult.kt │ │ │ │ └── OnlineSearchScreen.kt │ │ │ └── settings │ │ │ │ ├── AboutScreen.kt │ │ │ │ ├── AppearanceSettings.kt │ │ │ │ ├── BackupAndRestore.kt │ │ │ │ ├── ContentSettings.kt │ │ │ │ ├── PlayerSettings.kt │ │ │ │ ├── PrivacySettings.kt │ │ │ │ ├── SettingsScreen.kt │ │ │ │ └── StorageSettings.kt │ │ ├── theme │ │ │ └── Theme.kt │ │ └── utils │ │ │ ├── AppBar.kt │ │ │ ├── FadingEdge.kt │ │ │ ├── HorizontalPager.kt │ │ │ ├── LazyGridSnapLayoutInfoProvider.kt │ │ │ ├── NavControllerUtils.kt │ │ │ ├── ScrollUtils.kt │ │ │ ├── ShapeUtils.kt │ │ │ ├── StringUtils.kt │ │ │ └── YouTubeUtils.kt │ │ ├── utils │ │ ├── CoilBitmapLoader.kt │ │ ├── ComposeDebugUtils.kt │ │ ├── DataStore.kt │ │ └── StringUtils.kt │ │ └── viewmodels │ │ ├── AccountViewModel.kt │ │ ├── AlbumViewModel.kt │ │ ├── ArtistItemsViewModel.kt │ │ ├── ArtistViewModel.kt │ │ ├── BackupRestoreViewModel.kt │ │ ├── HistoryViewModel.kt │ │ ├── HomeViewModel.kt │ │ ├── LibraryViewModels.kt │ │ ├── LocalPlaylistViewModel.kt │ │ ├── LocalSearchViewModel.kt │ │ ├── LyricsMenuViewModel.kt │ │ ├── MoodAndGenresViewModel.kt │ │ ├── NewReleaseViewModel.kt │ │ ├── OnlinePlaylistViewModel.kt │ │ ├── OnlineSearchSuggestionViewModel.kt │ │ ├── OnlineSearchViewModel.kt │ │ ├── StatsViewModel.kt │ │ └── YouTubeBrowseViewModel.kt │ └── res │ ├── drawable-hdpi │ ├── ic_stat_onesignal_default.png │ └── splash_image.png │ ├── drawable-mdpi │ ├── ic_stat_onesignal_default.png │ └── splash_image.png │ ├── drawable-xhdpi │ ├── ic_stat_onesignal_default.png │ └── splash_image.png │ ├── drawable-xxhdpi │ ├── ic_stat_onesignal_default.png │ └── splash_image.png │ ├── drawable-xxxhdpi │ ├── ic_stat_onesignal_default.png │ └── splash_image.png │ ├── drawable │ ├── add.xml │ ├── album.xml │ ├── app_logo.png │ ├── arrow_back.xml │ ├── arrow_downward.xml │ ├── arrow_forward.xml │ ├── arrow_top_left.xml │ ├── arrow_upward.xml │ ├── artist.xml │ ├── backup.xml │ ├── bedtime.xml │ ├── bookmark.xml │ ├── bookmark_filled.xml │ ├── buymeacoffee.xml │ ├── cached.xml │ ├── casino.xml │ ├── clear_all.xml │ ├── close.xml │ ├── contrast.xml │ ├── dark_mode.xml │ ├── delete.xml │ ├── discover_tune.xml │ ├── download.xml │ ├── drag_handle.xml │ ├── edit.xml │ ├── equalizer.xml │ ├── error.xml │ ├── expand_less.xml │ ├── expand_more.xml │ ├── explicit.xml │ ├── favorite.xml │ ├── favorite_border.xml │ ├── github.xml │ ├── graphic_eq.xml │ ├── grid_view.xml │ ├── history.xml │ ├── home.xml │ ├── ic_launcher_background.xml │ ├── ic_launcher_foreground.xml │ ├── ic_launcher_round.png │ ├── info.xml │ ├── input.xml │ ├── language.xml │ ├── launcher_foreground.xml │ ├── launcher_monochrome.xml │ ├── liberapay.xml │ ├── library_add.xml │ ├── library_add_check.xml │ ├── library_music.xml │ ├── list.xml │ ├── location_on.xml │ ├── lock.xml │ ├── lock_open.xml │ ├── lyrics.xml │ ├── manage_search.xml │ ├── mood.xml │ ├── more_horiz.xml │ ├── more_vert.xml │ ├── music_note.xml │ ├── navigate_next.xml │ ├── offline.xml │ ├── palette.xml │ ├── pause.xml │ ├── person.xml │ ├── play.xml │ ├── playlist_add.xml │ ├── playlist_play.xml │ ├── queue_music.xml │ ├── radio.xml │ ├── radio_button_checked.xml │ ├── radio_button_unchecked.xml │ ├── remove.xml │ ├── repeat.xml │ ├── repeat_on.xml │ ├── repeat_one.xml │ ├── repeat_one_on.xml │ ├── replay.xml │ ├── restore.xml │ ├── search.xml │ ├── security.xml │ ├── settings.xml │ ├── share.xml │ ├── shortcut_albums.xml │ ├── shortcut_playlists.xml │ ├── shortcut_search.xml │ ├── shortcut_songs.xml │ ├── shuffle.xml │ ├── shuffle_on.xml │ ├── skip_next.xml │ ├── skip_previous.xml │ ├── slow_motion_video.xml │ ├── small_icon.xml │ ├── splash_image.png │ ├── storage.xml │ ├── sync.xml │ ├── tab.xml │ ├── translate.xml │ ├── trending_up.xml │ ├── tune.xml │ ├── update.xml │ └── volume_up.xml │ ├── font │ ├── amsterdam.ttf │ ├── arvo.ttf │ ├── bod.TTF │ ├── edutas.ttf │ ├── kenia.ttf │ ├── oswald.ttf │ ├── raleway1.ttf │ ├── raleway2.ttf │ ├── roboto1.ttf │ ├── roboto2.ttf │ └── teko.ttf │ ├── layout │ └── activity_splash.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── values-DE │ └── strings.xml │ ├── values-be │ └── strings.xml │ ├── values-bn-rIN │ └── strings.xml │ ├── values-cs │ └── strings.xml │ ├── values-es │ └── strings.xml │ ├── values-fa-rIR │ └── strings.xml │ ├── values-fi-rFI │ └── strings.xml │ ├── values-fr-rFR │ └── strings.xml │ ├── values-hu │ └── strings.xml │ ├── values-id │ └── strings.xml │ ├── values-it │ └── strings.xml │ ├── values-ja-rJP │ └── strings.xml │ ├── values-ko-rKR │ └── strings.xml │ ├── values-ml-rIN │ └── strings.xml │ ├── values-nl │ └── strings.xml │ ├── values-or-rIN │ └── strings.xml │ ├── values-pa │ └── strings.xml │ ├── values-pl │ └── strings.xml │ ├── values-pt-rBR │ └── strings.xml │ ├── values-ru-rRU │ └── strings.xml │ ├── values-sv-rSE │ └── strings.xml │ ├── values-tr │ └── strings.xml │ ├── values-uk-rUA │ └── strings.xml │ ├── values-vi │ └── strings.xml │ ├── values-zh-rCN │ └── strings.xml │ ├── values-zh-rTW │ └── strings.xml │ ├── values │ ├── app_name.xml │ ├── ic_launcher_background.xml │ ├── strings.xml │ ├── styles.xml │ └── values.xml │ ├── xml-v25 │ └── shortcuts.xml │ └── xml │ ├── automotive_app_desc.xml │ └── provider_paths.xml ├── assets ├── buymeacoffee.png └── liberapay.png ├── build.gradle.kts ├── crowdin.yml ├── fastlane └── metadata │ └── android │ ├── app_banner_youmusic.jpg │ ├── en-US │ ├── changelogs │ │ ├── 10.txt │ │ ├── 11.txt │ │ ├── 12.txt │ │ ├── 13.txt │ │ ├── 14.txt │ │ ├── 15.txt │ │ ├── 16.txt │ │ ├── 17.txt │ │ ├── 18.txt │ │ ├── 19.txt │ │ ├── 5.txt │ │ ├── 6.txt │ │ ├── 7.txt │ │ ├── 8.txt │ │ └── 9.txt │ ├── full_description.txt │ ├── images │ │ ├── icon.png │ │ └── phoneScreenshots │ │ │ ├── en1.png │ │ │ ├── en2.png │ │ │ ├── en3.png │ │ │ ├── en4.png │ │ │ ├── en5.png │ │ │ ├── en6.png │ │ │ ├── en7.png │ │ │ └── en8.png │ ├── short_description.txt │ └── title.txt │ ├── es │ ├── full_description.txt │ └── short_description.txt │ ├── it │ ├── full_description.txt │ ├── short_description.txt │ └── title.txt │ ├── ja │ ├── full_description.txt │ └── short_description.txt │ ├── ko │ ├── full_description.txt │ └── short_description.txt │ ├── pa │ ├── full_description.txt │ └── short_description.txt │ ├── ru-RU │ ├── full_description.txt │ ├── short_description.txt │ └── title.txt │ ├── tr │ ├── full_description.txt │ ├── images │ │ ├── icon.png │ │ └── phoneScreenshots │ │ │ ├── tr1.png │ │ │ ├── tr2.png │ │ │ ├── tr3.png │ │ │ ├── tr4.png │ │ │ ├── tr5.png │ │ │ ├── tr6.png │ │ │ ├── tr7.png │ │ │ └── tr8.png │ └── short_description.txt │ └── uk-UA │ ├── full_description.txt │ ├── short_description.txt │ └── title.txt ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── innertube ├── .gitignore ├── build.gradle.kts └── src │ ├── main │ └── java │ │ └── com │ │ └── zionhuang │ │ └── innertube │ │ ├── InnerTube.kt │ │ ├── YouTube.kt │ │ ├── encoder │ │ └── BrotliEncoder.kt │ │ ├── models │ │ ├── AccountInfo.kt │ │ ├── AutomixPreviewVideoRenderer.kt │ │ ├── Badges.kt │ │ ├── Button.kt │ │ ├── Context.kt │ │ ├── Continuation.kt │ │ ├── Endpoint.kt │ │ ├── GridRenderer.kt │ │ ├── Icon.kt │ │ ├── Menu.kt │ │ ├── MusicCardShelfRenderer.kt │ │ ├── MusicCarouselShelfRenderer.kt │ │ ├── MusicDescriptionShelfRenderer.kt │ │ ├── MusicNavigationButtonRenderer.kt │ │ ├── MusicPlaylistShelfRenderer.kt │ │ ├── MusicQueueRenderer.kt │ │ ├── MusicResponsiveListItemRenderer.kt │ │ ├── MusicShelfRenderer.kt │ │ ├── MusicTwoRowItemRenderer.kt │ │ ├── NavigationEndpoint.kt │ │ ├── PlaylistPanelRenderer.kt │ │ ├── PlaylistPanelVideoRenderer.kt │ │ ├── ResponseContext.kt │ │ ├── Runs.kt │ │ ├── SearchSuggestions.kt │ │ ├── SearchSuggestionsSectionRenderer.kt │ │ ├── SectionListRenderer.kt │ │ ├── Tabs.kt │ │ ├── ThumbnailRenderer.kt │ │ ├── Thumbnails.kt │ │ ├── YTItem.kt │ │ ├── YouTubeClient.kt │ │ ├── YouTubeLocale.kt │ │ ├── body │ │ │ ├── AccountMenuBody.kt │ │ │ ├── BrowseBody.kt │ │ │ ├── GetQueueBody.kt │ │ │ ├── GetSearchSuggestionsBody.kt │ │ │ ├── GetTranscriptBody.kt │ │ │ ├── NextBody.kt │ │ │ ├── PlayerBody.kt │ │ │ └── SearchBody.kt │ │ └── response │ │ │ ├── AccountMenuResponse.kt │ │ │ ├── BrowseResponse.kt │ │ │ ├── GetQueueResponse.kt │ │ │ ├── GetSearchSuggestionsResponse.kt │ │ │ ├── GetTranscriptResponse.kt │ │ │ ├── NextResponse.kt │ │ │ ├── PipedResponse.kt │ │ │ ├── PlayerResponse.kt │ │ │ └── SearchResponse.kt │ │ ├── pages │ │ ├── AlbumPage.kt │ │ ├── ArtistItemsContinuationPage.kt │ │ ├── ArtistItemsPage.kt │ │ ├── ArtistPage.kt │ │ ├── BrowseResult.kt │ │ ├── ExplorePage.kt │ │ ├── MoodAndGenres.kt │ │ ├── NewReleaseAlbumPage.kt │ │ ├── NextPage.kt │ │ ├── PlaylistContinuationPage.kt │ │ ├── PlaylistPage.kt │ │ ├── RelatedPage.kt │ │ ├── SearchPage.kt │ │ ├── SearchSuggestionPage.kt │ │ └── SearchSummaryPage.kt │ │ └── utils │ │ └── Utils.kt │ └── test │ └── java │ └── com │ └── zionhuang │ └── innertube │ └── YouTubeTest.kt ├── kugou ├── .gitignore ├── build.gradle.kts └── src │ ├── main │ └── java │ │ └── com │ │ └── zionhuang │ │ └── kugou │ │ ├── KuGou.kt │ │ └── models │ │ ├── DownloadLyricsResponse.kt │ │ ├── SearchLyricsResponse.kt │ │ └── SearchSongResponse.kt │ └── test │ └── java │ └── Test.kt ├── lint.xml ├── material-color-utilities ├── .gitignore ├── build.gradle.kts └── src │ └── main │ └── java │ └── com │ └── google │ └── material │ └── color │ ├── blend │ └── Blend.java │ ├── contrast │ └── Contrast.java │ ├── dislike │ └── DislikeAnalyzer.java │ ├── dynamiccolor │ ├── DynamicColor.java │ ├── ToneDeltaConstraint.java │ └── TonePolarity.java │ ├── hct │ ├── Cam16.java │ ├── Hct.java │ ├── HctSolver.java │ └── ViewingConditions.java │ ├── palettes │ ├── CorePalette.java │ └── TonalPalette.java │ ├── quantize │ ├── PointProvider.java │ ├── PointProviderLab.java │ ├── Quantizer.java │ ├── QuantizerCelebi.java │ ├── QuantizerMap.java │ ├── QuantizerResult.java │ ├── QuantizerWsmeans.java │ └── QuantizerWu.java │ ├── scheme │ ├── DynamicScheme.java │ ├── Scheme.java │ ├── SchemeExpressive.java │ ├── SchemeFidelity.java │ ├── SchemeMonochrome.java │ ├── SchemeNeutral.java │ ├── SchemeTonalSpot.java │ ├── SchemeVibrant.java │ └── Variant.java │ ├── score │ └── Score.java │ ├── temperature │ └── TemperatureCache.java │ └── utils │ ├── ColorUtils.java │ ├── MathUtils.java │ └── StringUtils.java └── settings.gradle.kts /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/workflows/android.yml: -------------------------------------------------------------------------------- 1 | name: Android CI 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v3 16 | - name: set up JDK 11 17 | uses: actions/setup-java@v3 18 | with: 19 | java-version: '11' 20 | distribution: 'temurin' 21 | cache: gradle 22 | 23 | - name: Grant execute permission for gradlew 24 | run: chmod +x gradlew 25 | - name: Build with Gradle 26 | run: ./gradlew build 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Gradle files 2 | .gradle/ 3 | build/ 4 | 5 | # Local configuration file (sdk path, etc) 6 | local.properties 7 | 8 | # Log/OS Files 9 | *.log 10 | 11 | # Android Studio generated files and folders 12 | captures/ 13 | .externalNativeBuild/ 14 | .cxx/ 15 | *.apk 16 | output.json 17 | 18 | # IntelliJ 19 | *.iml 20 | .idea/ 21 | 22 | # Keystore files 23 | *.jks 24 | *.keystore 25 | 26 | # Google Services (e.g. APIs or Firebase) 27 | google-services.json 28 | 29 | # Android Profiling 30 | *.hprof 31 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/CMakeLists.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/app/CMakeLists.txt -------------------------------------------------------------------------------- /app/foss/release/output-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "artifactType": { 4 | "type": "APK", 5 | "kind": "Directory" 6 | }, 7 | "applicationId": "com.ozsoft.youmusic", 8 | "variantName": "fossRelease", 9 | "elements": [ 10 | { 11 | "type": "SINGLE", 12 | "filters": [], 13 | "attributes": [], 14 | "versionCode": 13, 15 | "versionName": "1.0.5", 16 | "outputFile": "app-foss-release.apk" 17 | } 18 | ], 19 | "elementType": "File" 20 | } -------------------------------------------------------------------------------- /app/full/release/app-full-release.aab: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/app/full/release/app-full-release.aab -------------------------------------------------------------------------------- /app/full/release/output-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "artifactType": { 4 | "type": "APK", 5 | "kind": "Directory" 6 | }, 7 | "applicationId": "com.ozsoft.youmusic", 8 | "variantName": "fullRelease", 9 | "elements": [ 10 | { 11 | "type": "SINGLE", 12 | "filters": [], 13 | "attributes": [], 14 | "versionCode": 14, 15 | "versionName": "1.0.7", 16 | "outputFile": "app-full-release.apk" 17 | } 18 | ], 19 | "elementType": "File" 20 | } -------------------------------------------------------------------------------- /app/src/debug/res/values/app_name.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | YouMusic 4 | 107606197596 5 | -------------------------------------------------------------------------------- /app/src/foss/java/com/zionhuang/music/utils/TranslationHelper.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.utils 2 | 3 | import com.zionhuang.music.db.entities.LyricsEntity 4 | 5 | object TranslationHelper { 6 | suspend fun translate(lyrics: LyricsEntity): LyricsEntity = lyrics 7 | suspend fun clearModels() {} 8 | } -------------------------------------------------------------------------------- /app/src/foss/java/com/zionhuang/music/utils/Utils.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.utils 2 | 3 | import com.zionhuang.music.MainActivity 4 | import java.lang.Exception 5 | 6 | fun MainActivity.setupRemoteConfig() {} 7 | 8 | fun reportException(throwable: Throwable) { 9 | throwable.printStackTrace() 10 | } 11 | -------------------------------------------------------------------------------- /app/src/full/java/com/zionhuang/music/utils/Utils.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.utils 2 | 3 | import com.google.firebase.crashlytics.ktx.crashlytics 4 | import com.google.firebase.ktx.Firebase 5 | import com.google.firebase.remoteconfig.ConfigUpdate 6 | import com.google.firebase.remoteconfig.ConfigUpdateListener 7 | import com.google.firebase.remoteconfig.FirebaseRemoteConfigException 8 | import com.google.firebase.remoteconfig.ktx.remoteConfig 9 | import com.google.firebase.remoteconfig.ktx.remoteConfigSettings 10 | import com.zionhuang.music.MainActivity 11 | import kotlin.time.Duration.Companion.hours 12 | 13 | fun MainActivity.setupRemoteConfig() { 14 | val remoteConfig = Firebase.remoteConfig 15 | remoteConfig.setConfigSettingsAsync(remoteConfigSettings { 16 | minimumFetchIntervalInSeconds = 6.hours.inWholeSeconds 17 | }) 18 | remoteConfig.fetchAndActivate() 19 | .addOnCompleteListener(this) { task -> 20 | if (task.isSuccessful) { 21 | latestVersion = remoteConfig.getLong("latest_version") 22 | } 23 | } 24 | remoteConfig.addOnConfigUpdateListener(object : ConfigUpdateListener { 25 | override fun onError(error: FirebaseRemoteConfigException) {} 26 | override fun onUpdate(configUpdate: ConfigUpdate) { 27 | remoteConfig.activate().addOnCompleteListener { 28 | latestVersion = remoteConfig.getLong("latest_version") 29 | } 30 | } 31 | }) 32 | } 33 | 34 | fun reportException(throwable: Throwable) { 35 | Firebase.crashlytics.recordException(throwable) 36 | throwable.printStackTrace() 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/SplashActivity.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Intent 5 | import android.os.Bundle 6 | import androidx.appcompat.app.AppCompatActivity 7 | 8 | @SuppressLint("CustomSplashScreen") 9 | class SplashActivity: AppCompatActivity() { 10 | private var activityStarted = false 11 | 12 | override fun onCreate(savedInstanceState: Bundle?) { 13 | super.onCreate(savedInstanceState) 14 | setContentView(R.layout.activity_splash) 15 | activityStarted = true 16 | val timerThread: Thread = object : Thread() { 17 | override fun run() { 18 | try { 19 | sleep(3000) 20 | } catch (e: InterruptedException) { 21 | e.printStackTrace() 22 | } finally { 23 | if (activityStarted && intent != null && intent.flags and Intent.FLAG_ACTIVITY_REORDER_TO_FRONT !== 0) { 24 | finish() 25 | } else { 26 | val intent = Intent(this@SplashActivity, MainActivity::class.java) 27 | startActivity(intent) 28 | } 29 | } 30 | } 31 | } 32 | timerThread.start() 33 | } 34 | 35 | override fun onPause() { 36 | super.onPause() 37 | finish() 38 | } 39 | } -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/constants/Constants.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.constants 2 | 3 | object Constants { 4 | const val GITHUB_URL = "https://github.com/TeamYouDown/YouMusic" 5 | const val YOU_MUSIC_URL = "https://youdown.net" 6 | const val GITHUB_ISSUE_URL = "https://github.com/TeamYouDown/YouMusic/issues" 7 | const val TEXT_NAME = "YouMusic" 8 | const val TEXT_COMPANY = "TeamYouDown" 9 | const val ONESIGNAL_CODE = "f82119dc-c269-406b-98cd-f2075e581005" 10 | } -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/constants/Dimensions.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.constants 2 | 3 | import androidx.compose.animation.core.Spring 4 | import androidx.compose.animation.core.spring 5 | import androidx.compose.ui.unit.Dp 6 | import androidx.compose.ui.unit.dp 7 | 8 | const val CONTENT_TYPE_HEADER = 0 9 | const val CONTENT_TYPE_LIST = 1 10 | const val CONTENT_TYPE_SONG = 2 11 | const val CONTENT_TYPE_ARTIST = 3 12 | const val CONTENT_TYPE_ALBUM = 4 13 | const val CONTENT_TYPE_PLAYLIST = 5 14 | 15 | val NavigationBarHeight = 80.dp 16 | val MiniPlayerHeight = 64.dp 17 | val QueuePeekHeight = 64.dp 18 | val AppBarHeight = 64.dp 19 | 20 | val ListItemHeight = 64.dp 21 | val SuggestionItemHeight = 56.dp 22 | val SearchFilterHeight = 48.dp 23 | val ListThumbnailSize = 48.dp 24 | val GridThumbnailHeight = 128.dp 25 | val AlbumThumbnailSize = 144.dp 26 | 27 | val ThumbnailCornerRadius = 6.dp 28 | 29 | val PlayerHorizontalPadding = 32.dp 30 | 31 | val NavigationBarAnimationSpec = spring(stiffness = Spring.StiffnessMediumLow) 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/constants/MediaSessionConstants.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.constants 2 | 3 | import android.os.Bundle 4 | import androidx.media3.session.SessionCommand 5 | 6 | object MediaSessionConstants { 7 | const val ACTION_TOGGLE_LIBRARY = "TOGGLE_LIBRARY" 8 | const val ACTION_TOGGLE_LIKE = "TOGGLE_LIKE" 9 | const val ACTION_TOGGLE_SHUFFLE = "TOGGLE_SHUFFLE" 10 | const val ACTION_TOGGLE_REPEAT_MODE = "TOGGLE_REPEAT_MODE" 11 | val CommandToggleLibrary = SessionCommand(ACTION_TOGGLE_LIBRARY, Bundle.EMPTY) 12 | val CommandToggleLike = SessionCommand(ACTION_TOGGLE_LIKE, Bundle.EMPTY) 13 | val CommandToggleShuffle = SessionCommand(ACTION_TOGGLE_SHUFFLE, Bundle.EMPTY) 14 | val CommandToggleRepeatMode = SessionCommand(ACTION_TOGGLE_REPEAT_MODE, Bundle.EMPTY) 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/constants/StatPeriod.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.constants 2 | 3 | import java.time.LocalDateTime 4 | import java.time.ZoneOffset 5 | 6 | enum class StatPeriod { 7 | `1_WEEK`, `1_MONTH`, `3_MONTH`, `6_MONTH`, `1_YEAR`, ALL; 8 | 9 | fun toTimeMillis(): Long = 10 | when (this) { 11 | `1_WEEK` -> LocalDateTime.now().minusWeeks(1).toInstant(ZoneOffset.UTC).toEpochMilli() 12 | `1_MONTH` -> LocalDateTime.now().minusMonths(1).toInstant(ZoneOffset.UTC).toEpochMilli() 13 | `3_MONTH` -> LocalDateTime.now().minusMonths(3).toInstant(ZoneOffset.UTC).toEpochMilli() 14 | `6_MONTH` -> LocalDateTime.now().minusMonths(6).toInstant(ZoneOffset.UTC).toEpochMilli() 15 | `1_YEAR` -> LocalDateTime.now().minusMonths(12).toInstant(ZoneOffset.UTC).toEpochMilli() 16 | ALL -> 0 17 | } 18 | } -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/db/Converters.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.db 2 | 3 | import androidx.room.TypeConverter 4 | import java.time.Instant 5 | import java.time.LocalDateTime 6 | import java.time.ZoneOffset 7 | 8 | class Converters { 9 | @TypeConverter 10 | fun fromTimestamp(value: Long?): LocalDateTime? = 11 | if (value != null) LocalDateTime.ofInstant(Instant.ofEpochMilli(value), ZoneOffset.UTC) 12 | else null 13 | 14 | @TypeConverter 15 | fun dateToTimestamp(date: LocalDateTime?): Long? = 16 | date?.atZone(ZoneOffset.UTC)?.toInstant()?.toEpochMilli() 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/db/entities/Album.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.db.entities 2 | 3 | import androidx.compose.runtime.Immutable 4 | import androidx.room.Embedded 5 | import androidx.room.Junction 6 | import androidx.room.Relation 7 | 8 | @Immutable 9 | data class Album( 10 | @Embedded 11 | val album: AlbumEntity, 12 | @Relation( 13 | entity = ArtistEntity::class, 14 | entityColumn = "id", 15 | parentColumn = "id", 16 | associateBy = Junction( 17 | value = AlbumArtistMap::class, 18 | parentColumn = "albumId", 19 | entityColumn = "artistId" 20 | ) 21 | ) 22 | val artists: List, 23 | ) : LocalItem() { 24 | override val id: String 25 | get() = album.id 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/db/entities/AlbumArtistMap.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.db.entities 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.ForeignKey 6 | 7 | @Entity( 8 | tableName = "album_artist_map", 9 | primaryKeys = ["albumId", "artistId"], 10 | foreignKeys = [ 11 | ForeignKey( 12 | entity = AlbumEntity::class, 13 | parentColumns = ["id"], 14 | childColumns = ["albumId"], 15 | onDelete = ForeignKey.CASCADE), 16 | ForeignKey( 17 | entity = ArtistEntity::class, 18 | parentColumns = ["id"], 19 | childColumns = ["artistId"], 20 | onDelete = ForeignKey.CASCADE 21 | ) 22 | ] 23 | ) 24 | data class AlbumArtistMap( 25 | @ColumnInfo(index = true) val albumId: String, 26 | @ColumnInfo(index = true) val artistId: String, 27 | val order: Int, 28 | ) 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/db/entities/AlbumEntity.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.db.entities 2 | 3 | import androidx.compose.runtime.Immutable 4 | import androidx.room.Entity 5 | import androidx.room.PrimaryKey 6 | import java.time.LocalDateTime 7 | 8 | @Immutable 9 | @Entity(tableName = "album") 10 | data class AlbumEntity( 11 | @PrimaryKey val id: String, 12 | val title: String, 13 | val year: Int? = null, 14 | val thumbnailUrl: String? = null, 15 | val themeColor: Int? = null, 16 | val songCount: Int, 17 | val duration: Int, 18 | val lastUpdateTime: LocalDateTime = LocalDateTime.now(), 19 | val bookmarkedAt: LocalDateTime? = null, 20 | ) { 21 | fun toggleLike() = copy( 22 | bookmarkedAt = if (bookmarkedAt != null) null else LocalDateTime.now() 23 | ) 24 | } -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/db/entities/AlbumWithSongs.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.db.entities 2 | 3 | import androidx.compose.runtime.Immutable 4 | import androidx.room.Embedded 5 | import androidx.room.Junction 6 | import androidx.room.Relation 7 | 8 | @Immutable 9 | data class AlbumWithSongs( 10 | @Embedded 11 | val album: AlbumEntity, 12 | @Relation( 13 | entity = ArtistEntity::class, 14 | entityColumn = "id", 15 | parentColumn = "id", 16 | associateBy = Junction( 17 | value = AlbumArtistMap::class, 18 | parentColumn = "albumId", 19 | entityColumn = "artistId" 20 | ) 21 | ) 22 | val artists: List, 23 | @Relation( 24 | entity = SongEntity::class, 25 | entityColumn = "id", 26 | parentColumn = "id", 27 | associateBy = Junction( 28 | value = SortedSongAlbumMap::class, 29 | parentColumn = "albumId", 30 | entityColumn = "songId" 31 | ) 32 | ) 33 | val songs: List, 34 | ) 35 | -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/db/entities/Artist.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.db.entities 2 | 3 | import androidx.compose.runtime.Immutable 4 | import androidx.room.Embedded 5 | 6 | @Immutable 7 | data class Artist( 8 | @Embedded 9 | val artist: ArtistEntity, 10 | val songCount: Int, 11 | ) : LocalItem() { 12 | override val id: String 13 | get() = artist.id 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/db/entities/ArtistEntity.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.db.entities 2 | 3 | import androidx.compose.runtime.Immutable 4 | import androidx.room.Entity 5 | import androidx.room.PrimaryKey 6 | import org.apache.commons.lang3.RandomStringUtils 7 | import java.time.LocalDateTime 8 | 9 | @Immutable 10 | @Entity(tableName = "artist") 11 | data class ArtistEntity( 12 | @PrimaryKey val id: String, 13 | val name: String, 14 | val thumbnailUrl: String? = null, 15 | val lastUpdateTime: LocalDateTime = LocalDateTime.now(), 16 | val bookmarkedAt: LocalDateTime? = null, 17 | ) { 18 | val isYouTubeArtist: Boolean 19 | get() = id.startsWith("UC") 20 | 21 | val isLocalArtist: Boolean 22 | get() = id.startsWith("LA") 23 | 24 | fun toggleLike() = copy( 25 | bookmarkedAt = if (bookmarkedAt != null) null else LocalDateTime.now() 26 | ) 27 | 28 | companion object { 29 | fun generateArtistId() = "LA" + RandomStringUtils.random(8, true, false) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/db/entities/Event.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.db.entities 2 | 3 | import androidx.compose.runtime.Immutable 4 | import androidx.room.ColumnInfo 5 | import androidx.room.Entity 6 | import androidx.room.ForeignKey 7 | import androidx.room.PrimaryKey 8 | import java.time.LocalDateTime 9 | 10 | @Immutable 11 | @Entity( 12 | tableName = "event", 13 | foreignKeys = [ 14 | ForeignKey( 15 | entity = SongEntity::class, 16 | parentColumns = ["id"], 17 | childColumns = ["songId"], 18 | onDelete = ForeignKey.CASCADE 19 | ) 20 | ] 21 | ) 22 | data class Event( 23 | @PrimaryKey(autoGenerate = true) val id: Long = 0, 24 | @ColumnInfo(index = true) val songId: String, 25 | val timestamp: LocalDateTime, 26 | val playTime: Long, 27 | ) 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/db/entities/EventWithSong.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.db.entities 2 | 3 | import androidx.compose.runtime.Immutable 4 | import androidx.room.Embedded 5 | import androidx.room.Relation 6 | 7 | @Immutable 8 | data class EventWithSong( 9 | @Embedded 10 | val event: Event, 11 | @Relation( 12 | entity = SongEntity::class, 13 | parentColumn = "songId", 14 | entityColumn = "id" 15 | ) 16 | val song: Song, 17 | ) 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/db/entities/FormatEntity.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.db.entities 2 | 3 | import androidx.room.Entity 4 | import androidx.room.PrimaryKey 5 | 6 | @Entity(tableName = "format") 7 | data class FormatEntity( 8 | @PrimaryKey val id: String, 9 | val itag: Int, 10 | val mimeType: String, 11 | val codecs: String, 12 | val bitrate: Int, 13 | val sampleRate: Int?, 14 | val contentLength: Long, 15 | val loudnessDb: Double?, 16 | ) 17 | -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/db/entities/LocalItem.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.db.entities 2 | 3 | sealed class LocalItem { 4 | abstract val id: String 5 | } -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/db/entities/LyricsEntity.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.db.entities 2 | 3 | import androidx.room.Entity 4 | import androidx.room.PrimaryKey 5 | 6 | @Entity(tableName = "lyrics") 7 | data class LyricsEntity( 8 | @PrimaryKey val id: String, 9 | val lyrics: String, 10 | ) { 11 | companion object { 12 | const val LYRICS_NOT_FOUND = "LYRICS_NOT_FOUND" 13 | } 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/db/entities/Playlist.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.db.entities 2 | 3 | import androidx.compose.runtime.Immutable 4 | import androidx.room.Embedded 5 | import androidx.room.Junction 6 | import androidx.room.Relation 7 | 8 | @Immutable 9 | data class Playlist( 10 | @Embedded 11 | val playlist: PlaylistEntity, 12 | val songCount: Int, 13 | @Relation( 14 | entity = SongEntity::class, 15 | entityColumn = "id", 16 | parentColumn = "id", 17 | projection = ["thumbnailUrl"], 18 | associateBy = Junction( 19 | value = PlaylistSongMapPreview::class, 20 | parentColumn = "playlistId", 21 | entityColumn = "songId" 22 | ) 23 | ) 24 | val thumbnails: List, 25 | ) : LocalItem() { 26 | override val id: String 27 | get() = playlist.id 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/db/entities/PlaylistEntity.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.db.entities 2 | 3 | import androidx.compose.runtime.Immutable 4 | import androidx.room.Entity 5 | import androidx.room.PrimaryKey 6 | import org.apache.commons.lang3.RandomStringUtils 7 | 8 | @Immutable 9 | @Entity(tableName = "playlist") 10 | data class PlaylistEntity( 11 | @PrimaryKey val id: String = generatePlaylistId(), 12 | val name: String, 13 | val browseId: String? = null, 14 | ) { 15 | companion object { 16 | const val LIKED_PLAYLIST_ID = "LP_LIKED" 17 | const val DOWNLOADED_PLAYLIST_ID = "LP_DOWNLOADED" 18 | 19 | fun generatePlaylistId() = "LP" + RandomStringUtils.random(8, true, false) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/db/entities/PlaylistSong.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.db.entities 2 | 3 | import androidx.room.Embedded 4 | import androidx.room.Relation 5 | 6 | data class PlaylistSong( 7 | @Embedded val map: PlaylistSongMap, 8 | @Relation( 9 | parentColumn = "songId", 10 | entityColumn = "id", 11 | entity = SongEntity::class 12 | ) 13 | val song: Song, 14 | ) 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/db/entities/PlaylistSongMap.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.db.entities 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.ForeignKey 6 | import androidx.room.PrimaryKey 7 | 8 | @Entity( 9 | tableName = "playlist_song_map", 10 | foreignKeys = [ 11 | ForeignKey( 12 | entity = PlaylistEntity::class, 13 | parentColumns = ["id"], 14 | childColumns = ["playlistId"], 15 | onDelete = ForeignKey.CASCADE 16 | ), 17 | ForeignKey( 18 | entity = SongEntity::class, 19 | parentColumns = ["id"], 20 | childColumns = ["songId"], 21 | onDelete = ForeignKey.CASCADE) 22 | ] 23 | ) 24 | data class PlaylistSongMap( 25 | @PrimaryKey(autoGenerate = true) val id: Int = 0, 26 | @ColumnInfo(index = true) val playlistId: String, 27 | @ColumnInfo(index = true) val songId: String, 28 | val position: Int = 0, 29 | ) 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/db/entities/PlaylistSongMapPreview.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.db.entities 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.DatabaseView 5 | 6 | @DatabaseView( 7 | viewName = "playlist_song_map_preview", 8 | value = "SELECT * FROM playlist_song_map WHERE position <= 3 ORDER BY position") 9 | data class PlaylistSongMapPreview( 10 | @ColumnInfo(index = true) val playlistId: String, 11 | @ColumnInfo(index = true) val songId: String, 12 | val idInPlaylist: Int = 0, 13 | ) 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/db/entities/RelatedSongMap.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.db.entities 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.ForeignKey 6 | import androidx.room.PrimaryKey 7 | 8 | @Entity( 9 | tableName = "related_song_map", 10 | foreignKeys = [ 11 | ForeignKey( 12 | entity = SongEntity::class, 13 | parentColumns = ["id"], 14 | childColumns = ["songId"], 15 | onDelete = ForeignKey.CASCADE 16 | ), 17 | ForeignKey( 18 | entity = SongEntity::class, 19 | parentColumns = ["id"], 20 | childColumns = ["relatedSongId"], 21 | onDelete = ForeignKey.CASCADE 22 | ) 23 | ] 24 | ) 25 | data class RelatedSongMap( 26 | @PrimaryKey(autoGenerate = true) val id: Long = 0, 27 | @ColumnInfo(index = true) val songId: String, 28 | @ColumnInfo(index = true) val relatedSongId: String, 29 | ) 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/db/entities/SearchHistory.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.db.entities 2 | 3 | import androidx.room.Entity 4 | import androidx.room.Index 5 | import androidx.room.PrimaryKey 6 | 7 | @Entity( 8 | tableName = "search_history", 9 | indices = [Index( 10 | value = ["query"], 11 | unique = true 12 | )] 13 | ) 14 | data class SearchHistory( 15 | @PrimaryKey(autoGenerate = true) val id: Long = 0, 16 | val query: String, 17 | ) 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/db/entities/Song.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.db.entities 2 | 3 | import androidx.compose.runtime.Immutable 4 | import androidx.room.Embedded 5 | import androidx.room.Junction 6 | import androidx.room.Relation 7 | 8 | @Immutable 9 | data class Song @JvmOverloads constructor( 10 | @Embedded val song: SongEntity, 11 | @Relation( 12 | entity = ArtistEntity::class, 13 | entityColumn = "id", 14 | parentColumn = "id", 15 | associateBy = Junction( 16 | value = SortedSongArtistMap::class, 17 | parentColumn = "songId", 18 | entityColumn = "artistId" 19 | ) 20 | ) 21 | val artists: List, 22 | @Relation( 23 | entity = AlbumEntity::class, 24 | entityColumn = "id", 25 | parentColumn = "id", 26 | associateBy = Junction( 27 | value = SongAlbumMap::class, 28 | parentColumn = "songId", 29 | entityColumn = "albumId" 30 | ) 31 | ) 32 | val album: AlbumEntity? = null, 33 | ) : LocalItem() { 34 | override val id: String 35 | get() = song.id 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/db/entities/SongAlbumMap.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.db.entities 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.ForeignKey 6 | 7 | @Entity( 8 | tableName = "song_album_map", 9 | primaryKeys = ["songId", "albumId"], 10 | foreignKeys = [ 11 | ForeignKey( 12 | entity = SongEntity::class, 13 | parentColumns = ["id"], 14 | childColumns = ["songId"], 15 | onDelete = ForeignKey.CASCADE), 16 | ForeignKey( 17 | entity = AlbumEntity::class, 18 | parentColumns = ["id"], 19 | childColumns = ["albumId"], 20 | onDelete = ForeignKey.CASCADE 21 | ) 22 | ] 23 | ) 24 | data class SongAlbumMap( 25 | @ColumnInfo(index = true) val songId: String, 26 | @ColumnInfo(index = true) val albumId: String, 27 | val index: Int, 28 | ) 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/db/entities/SongArtistMap.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.db.entities 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.ForeignKey 6 | 7 | @Entity( 8 | tableName = "song_artist_map", 9 | primaryKeys = ["songId", "artistId"], 10 | foreignKeys = [ 11 | ForeignKey( 12 | entity = SongEntity::class, 13 | parentColumns = ["id"], 14 | childColumns = ["songId"], 15 | onDelete = ForeignKey.CASCADE), 16 | ForeignKey( 17 | entity = ArtistEntity::class, 18 | parentColumns = ["id"], 19 | childColumns = ["artistId"], 20 | onDelete = ForeignKey.CASCADE 21 | ) 22 | ] 23 | ) 24 | data class SongArtistMap( 25 | @ColumnInfo(index = true) val songId: String, 26 | @ColumnInfo(index = true) val artistId: String, 27 | val position: Int, 28 | ) 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/db/entities/SongEntity.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.db.entities 2 | 3 | import androidx.compose.runtime.Immutable 4 | import androidx.room.Entity 5 | import androidx.room.Index 6 | import androidx.room.PrimaryKey 7 | import java.time.LocalDateTime 8 | 9 | @Immutable 10 | @Entity( 11 | tableName = "song", 12 | indices = [ 13 | Index( 14 | value = ["albumId"] 15 | ) 16 | ] 17 | ) 18 | data class SongEntity( 19 | @PrimaryKey val id: String, 20 | val title: String, 21 | val duration: Int = -1, // in seconds 22 | val thumbnailUrl: String? = null, 23 | val albumId: String? = null, 24 | val albumName: String? = null, 25 | val liked: Boolean = false, 26 | val totalPlayTime: Long = 0, // in milliseconds 27 | val inLibrary: LocalDateTime? = null, 28 | ) { 29 | fun toggleLike() = copy( 30 | liked = !liked, 31 | inLibrary = if (!liked) inLibrary ?: LocalDateTime.now() else inLibrary 32 | ) 33 | 34 | fun toggleLibrary() = copy(inLibrary = if (inLibrary == null) LocalDateTime.now() else null) 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/db/entities/SortedSongAlbumMap.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.db.entities 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.DatabaseView 5 | 6 | @DatabaseView( 7 | viewName = "sorted_song_album_map", 8 | value = "SELECT * FROM song_album_map ORDER BY `index`") 9 | data class SortedSongAlbumMap( 10 | @ColumnInfo(index = true) val songId: String, 11 | @ColumnInfo(index = true) val albumId: String, 12 | val index: Int, 13 | ) 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/db/entities/SortedSongArtistMap.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.db.entities 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.DatabaseView 5 | 6 | @DatabaseView( 7 | viewName = "sorted_song_artist_map", 8 | value = "SELECT * FROM song_artist_map ORDER BY position") 9 | data class SortedSongArtistMap( 10 | @ColumnInfo(index = true) val songId: String, 11 | @ColumnInfo(index = true) val artistId: String, 12 | val position: Int, 13 | ) 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/extensions/CoroutineExt.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.extensions 2 | 3 | import kotlinx.coroutines.CoroutineExceptionHandler 4 | import kotlinx.coroutines.CoroutineScope 5 | import kotlinx.coroutines.flow.Flow 6 | import kotlinx.coroutines.flow.collectLatest 7 | import kotlinx.coroutines.launch 8 | 9 | fun Flow.collect(scope: CoroutineScope, action: suspend (value: T) -> Unit) { 10 | scope.launch { 11 | collect(action) 12 | } 13 | } 14 | 15 | fun Flow.collectLatest(scope: CoroutineScope, action: suspend (value: T) -> Unit) { 16 | scope.launch { 17 | collectLatest(action) 18 | } 19 | } 20 | 21 | val SilentHandler = CoroutineExceptionHandler { _, _ -> } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/extensions/FileExt.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.extensions 2 | 3 | import java.io.File 4 | import java.io.InputStream 5 | import java.io.OutputStream 6 | import java.util.zip.ZipInputStream 7 | import java.util.zip.ZipOutputStream 8 | 9 | operator fun File.div(child: String): File = File(this, child) 10 | 11 | fun InputStream.zipInputStream():ZipInputStream = ZipInputStream(this) 12 | fun OutputStream.zipOutputStream(): ZipOutputStream = ZipOutputStream(this) -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/extensions/ListExt.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.extensions 2 | 3 | fun List.reversed(reversed: Boolean) = if (reversed) asReversed() else this 4 | 5 | fun MutableList.move(fromIndex: Int, toIndex: Int): MutableList { 6 | add(toIndex, removeAt(fromIndex)) 7 | return this 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/extensions/StringExt.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.extensions 2 | 3 | import androidx.sqlite.db.SimpleSQLiteQuery 4 | import java.net.InetSocketAddress 5 | import java.net.InetSocketAddress.createUnresolved 6 | 7 | inline fun > String?.toEnum(defaultValue: T): T = 8 | if (this == null) defaultValue 9 | else try { 10 | enumValueOf(this) 11 | } catch (e: IllegalArgumentException) { 12 | defaultValue 13 | } 14 | 15 | fun String.toSQLiteQuery(): SimpleSQLiteQuery = SimpleSQLiteQuery(this) 16 | 17 | fun String.toInetSocketAddress(): InetSocketAddress { 18 | val (host, port) = split(":") 19 | return createUnresolved(host, port.toInt()) 20 | } -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/extensions/UtilExt.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.extensions 2 | 3 | fun tryOrNull(block: () -> T): T? = 4 | try { 5 | block() 6 | } catch (e: Exception) { 7 | null 8 | } -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/lyrics/KuGouLyricsProvider.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.lyrics 2 | 3 | import android.content.Context 4 | import com.zionhuang.kugou.KuGou 5 | import com.zionhuang.music.constants.EnableKugouKey 6 | import com.zionhuang.music.utils.dataStore 7 | import com.zionhuang.music.utils.get 8 | 9 | object KuGouLyricsProvider : LyricsProvider { 10 | override val name = "Kugou" 11 | override fun isEnabled(context: Context): Boolean = 12 | context.dataStore[EnableKugouKey] ?: true 13 | 14 | override suspend fun getLyrics(id: String, title: String, artist: String, duration: Int): Result = 15 | KuGou.getLyrics(title, artist, duration) 16 | 17 | override suspend fun getAllLyrics(id: String, title: String, artist: String, duration: Int, callback: (String) -> Unit) { 18 | KuGou.getAllLyrics(title, artist, duration, callback) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/lyrics/LyricsEntry.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.lyrics 2 | 3 | data class LyricsEntry( 4 | val time: Long, 5 | val text: String, 6 | ) : Comparable { 7 | override fun compareTo(other: LyricsEntry): Int = (time - other.time).toInt() 8 | 9 | companion object { 10 | val HEAD_LYRICS_ENTRY = LyricsEntry(0L, "") 11 | } 12 | } -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/lyrics/LyricsProvider.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.lyrics 2 | 3 | import android.content.Context 4 | 5 | interface LyricsProvider { 6 | val name: String 7 | fun isEnabled(context: Context): Boolean 8 | suspend fun getLyrics(id: String, title: String, artist: String, duration: Int): Result 9 | suspend fun getAllLyrics(id: String, title: String, artist: String, duration: Int, callback: (String) -> Unit) { 10 | getLyrics(id, title, artist, duration).onSuccess(callback) 11 | } 12 | } -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/lyrics/YouTubeLyricsProvider.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.lyrics 2 | 3 | import android.content.Context 4 | import com.zionhuang.innertube.YouTube 5 | import com.zionhuang.innertube.models.WatchEndpoint 6 | 7 | object YouTubeLyricsProvider : LyricsProvider { 8 | override val name = "YouMusic" 9 | override fun isEnabled(context: Context) = true 10 | override suspend fun getLyrics(id: String, title: String, artist: String, duration: Int): Result = runCatching { 11 | val nextResult = YouTube.next(WatchEndpoint(videoId = id)).getOrThrow() 12 | YouTube.lyrics( 13 | endpoint = nextResult.lyricsEndpoint ?: throw IllegalStateException("Lyrics endpoint not found") 14 | ).getOrThrow() ?: throw IllegalStateException("Lyrics unavailable") 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/lyrics/YouTubeSubtitleLyricsProvider.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.lyrics 2 | 3 | import android.content.Context 4 | import com.zionhuang.innertube.YouTube 5 | 6 | object YouTubeSubtitleLyricsProvider : LyricsProvider { 7 | override val name = "YouMusic" 8 | override fun isEnabled(context: Context) = true 9 | override suspend fun getLyrics(id: String, title: String, artist: String, duration: Int): Result = 10 | YouTube.transcript(id) 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/models/ItemsPage.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.models 2 | 3 | import com.zionhuang.innertube.models.YTItem 4 | 5 | data class ItemsPage( 6 | val items: List, 7 | val continuation: String?, 8 | ) 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/models/PersistQueue.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.models 2 | 3 | import java.io.Serializable 4 | 5 | data class PersistQueue( 6 | val title: String?, 7 | val items: List, 8 | val mediaItemIndex: Int, 9 | val position: Long, 10 | ) : Serializable 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/playback/queues/EmptyQueue.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.playback.queues 2 | 3 | import androidx.media3.common.MediaItem 4 | import com.zionhuang.music.models.MediaMetadata 5 | 6 | object EmptyQueue : Queue { 7 | override val preloadItem: MediaMetadata? = null 8 | override suspend fun getInitialStatus() = Queue.Status(null, emptyList(), -1) 9 | override fun hasNextPage() = false 10 | override suspend fun nextPage() = emptyList() 11 | } -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/playback/queues/ListQueue.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.playback.queues 2 | 3 | import androidx.media3.common.MediaItem 4 | import com.zionhuang.music.models.MediaMetadata 5 | 6 | class ListQueue( 7 | val title: String? = null, 8 | val items: List, 9 | val startIndex: Int = 0, 10 | val position: Long = 0L, 11 | ) : Queue { 12 | override val preloadItem: MediaMetadata? = null 13 | override suspend fun getInitialStatus() = Queue.Status(title, items, startIndex, position) 14 | override fun hasNextPage(): Boolean = false 15 | override suspend fun nextPage() = throw UnsupportedOperationException() 16 | } -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/playback/queues/Queue.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.playback.queues 2 | 3 | import androidx.media3.common.MediaItem 4 | import com.zionhuang.music.models.MediaMetadata 5 | 6 | interface Queue { 7 | val preloadItem: MediaMetadata? 8 | suspend fun getInitialStatus(): Status 9 | fun hasNextPage(): Boolean 10 | suspend fun nextPage(): List 11 | 12 | data class Status( 13 | val title: String?, 14 | val items: List, 15 | val mediaItemIndex: Int, 16 | val position: Long = 0L, 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/playback/queues/YouTubeQueue.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.playback.queues 2 | 3 | import androidx.media3.common.MediaItem 4 | import com.zionhuang.innertube.YouTube 5 | import com.zionhuang.innertube.models.WatchEndpoint 6 | import com.zionhuang.music.extensions.toMediaItem 7 | import com.zionhuang.music.models.MediaMetadata 8 | import kotlinx.coroutines.Dispatchers.IO 9 | import kotlinx.coroutines.withContext 10 | 11 | class YouTubeQueue( 12 | private var endpoint: WatchEndpoint, 13 | override val preloadItem: MediaMetadata? = null, 14 | ) : Queue { 15 | private var continuation: String? = null 16 | 17 | override suspend fun getInitialStatus(): Queue.Status { 18 | val nextResult = withContext(IO) { 19 | YouTube.next(endpoint, continuation).getOrThrow() 20 | } 21 | endpoint = nextResult.endpoint 22 | continuation = nextResult.continuation 23 | return Queue.Status( 24 | title = nextResult.title, 25 | items = nextResult.items.map { it.toMediaItem() }, 26 | mediaItemIndex = nextResult.currentIndex ?: 0 27 | ) 28 | } 29 | 30 | override fun hasNextPage(): Boolean = continuation != null 31 | 32 | override suspend fun nextPage(): List { 33 | val nextResult = withContext(IO) { 34 | YouTube.next(endpoint, continuation).getOrThrow() 35 | } 36 | endpoint = nextResult.endpoint 37 | continuation = nextResult.continuation 38 | return nextResult.items.map { it.toMediaItem() } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/ui/component/EmptyPlaceholder.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.ui.component 2 | 3 | import androidx.annotation.DrawableRes 4 | import androidx.compose.foundation.Image 5 | import androidx.compose.foundation.layout.* 6 | import androidx.compose.material3.MaterialTheme 7 | import androidx.compose.material3.Text 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.ui.Alignment 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.graphics.ColorFilter 12 | import androidx.compose.ui.res.painterResource 13 | import androidx.compose.ui.unit.dp 14 | 15 | @Composable 16 | fun EmptyPlaceholder( 17 | @DrawableRes icon: Int, 18 | text: String, 19 | modifier: Modifier = Modifier, 20 | ) { 21 | Column( 22 | horizontalAlignment = Alignment.CenterHorizontally, 23 | modifier = modifier 24 | .fillMaxSize() 25 | .padding(12.dp) 26 | ) { 27 | Image( 28 | painter = painterResource(icon), 29 | contentDescription = null, 30 | colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onBackground), 31 | modifier = Modifier.size(64.dp) 32 | ) 33 | 34 | Spacer(Modifier.height(12.dp)) 35 | 36 | Text( 37 | text = text, 38 | style = MaterialTheme.typography.bodyLarge 39 | ) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/ui/component/shimmer/ButtonPlaceholder.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.ui.component.shimmer 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.layout.Spacer 5 | import androidx.compose.foundation.layout.height 6 | import androidx.compose.foundation.shape.RoundedCornerShape 7 | import androidx.compose.material3.ButtonDefaults 8 | import androidx.compose.material3.MaterialTheme 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.draw.clip 12 | 13 | @Composable 14 | fun ButtonPlaceholder( 15 | modifier: Modifier = Modifier, 16 | ) { 17 | Spacer(modifier 18 | .height(ButtonDefaults.MinHeight) 19 | .clip(RoundedCornerShape(50)) 20 | .background(MaterialTheme.colorScheme.onSurface)) 21 | } -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/ui/component/shimmer/TextPlaceholder.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.ui.component.shimmer 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.layout.Spacer 5 | import androidx.compose.foundation.layout.fillMaxWidth 6 | import androidx.compose.foundation.layout.height 7 | import androidx.compose.foundation.layout.padding 8 | import androidx.compose.material3.MaterialTheme 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.runtime.remember 11 | import androidx.compose.ui.Modifier 12 | import androidx.compose.ui.unit.Dp 13 | import androidx.compose.ui.unit.dp 14 | import kotlin.random.Random 15 | 16 | @Composable 17 | fun TextPlaceholder( 18 | modifier: Modifier = Modifier, 19 | height: Dp = 16.dp, 20 | ) { 21 | Spacer( 22 | modifier = modifier 23 | .padding(vertical = 4.dp) 24 | .background(MaterialTheme.colorScheme.onSurface) 25 | .fillMaxWidth(remember { 0.25f + Random.nextFloat() * 0.5f }) 26 | .height(height) 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/ui/screens/Screens.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.ui.screens 2 | 3 | import androidx.annotation.DrawableRes 4 | import androidx.annotation.StringRes 5 | import androidx.compose.runtime.Immutable 6 | import com.zionhuang.music.R 7 | 8 | @Immutable 9 | sealed class Screens( 10 | @StringRes val titleId: Int, 11 | @DrawableRes val iconId: Int, 12 | val route: String, 13 | ) { 14 | object Home : Screens(R.string.home, R.drawable.home, "home") 15 | object Songs : Screens(R.string.songs, R.drawable.music_note, "songs") 16 | object Artists : Screens(R.string.artists, R.drawable.artist, "artists") 17 | object Albums : Screens(R.string.albums, R.drawable.album, "albums") 18 | object Playlists : Screens(R.string.playlists, R.drawable.queue_music, "playlists") 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/ui/utils/NavControllerUtils.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.ui.utils 2 | 3 | import androidx.navigation.NavController 4 | import androidx.navigation.NavGraph 5 | 6 | val NavController.canNavigateUp: Boolean 7 | get() = backQueue.count { entry -> entry.destination !is NavGraph } > 1 -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/ui/utils/ShapeUtils.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.ui.utils 2 | 3 | import androidx.compose.foundation.shape.CornerBasedShape 4 | import androidx.compose.foundation.shape.CornerSize 5 | import androidx.compose.ui.unit.dp 6 | 7 | fun CornerBasedShape.top(): CornerBasedShape = 8 | copy(bottomStart = CornerSize(0.dp), bottomEnd = CornerSize(0.dp)) -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/ui/utils/StringUtils.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.ui.utils 2 | 3 | import kotlin.math.absoluteValue 4 | 5 | fun formatFileSize(sizeBytes: Long): String { 6 | val prefix = if (sizeBytes < 0) "-" else "" 7 | var result: Long = sizeBytes.absoluteValue 8 | var suffix = "B" 9 | if (result > 900) { 10 | suffix = "KB" 11 | result /= 1024 12 | } 13 | if (result > 900) { 14 | suffix = "MB" 15 | result /= 1024 16 | } 17 | if (result > 900) { 18 | suffix = "GB" 19 | result /= 1024 20 | } 21 | if (result > 900) { 22 | suffix = "TB" 23 | result /= 1024 24 | } 25 | if (result > 900) { 26 | suffix = "PB" 27 | result /= 1024 28 | } 29 | return "$prefix$result $suffix" 30 | } -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/ui/utils/YouTubeUtils.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.ui.utils 2 | 3 | fun String.resize( 4 | width: Int? = null, 5 | height: Int? = null, 6 | ): String { 7 | if (width == null && height == null) return this 8 | "https://lh3\\.googleusercontent\\.com/.*=w(\\d+)-h(\\d+).*".toRegex().matchEntire(this)?.groupValues?.let { group -> 9 | val (W, H) = group.drop(1).map { it.toInt() } 10 | var w = width 11 | var h = height 12 | if (w != null && h == null) h = (w / W) * H 13 | if (w == null && h != null) w = (h / H) * W 14 | return "${split("=w")[0]}=w$w-h$h-p-l90-rj" 15 | } 16 | if (this matches "https://yt3\\.ggpht\\.com/.*=s(\\d+)".toRegex()) { 17 | return "$this-s${width ?: height}" 18 | } 19 | return this 20 | } -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/utils/StringUtils.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.utils 2 | 3 | import java.math.BigInteger 4 | import java.security.MessageDigest 5 | 6 | fun makeTimeString(duration: Long?): String { 7 | if (duration == null || duration < 0) return "" 8 | var sec = duration / 1000 9 | val day = sec / 86400 10 | sec %= 86400 11 | val hour = sec / 3600 12 | sec %= 3600 13 | val minute = sec / 60 14 | sec %= 60 15 | return when { 16 | day > 0 -> "%d:%02d:%02d:%02d".format(day, hour, minute, sec) 17 | hour > 0 -> "%d:%02d:%02d".format(hour, minute, sec) 18 | else -> "%d:%02d".format(minute, sec) 19 | } 20 | } 21 | 22 | fun md5(str: String): String { 23 | val md = MessageDigest.getInstance("MD5") 24 | return BigInteger(1, md.digest(str.toByteArray())).toString(16).padStart(32, '0') 25 | } 26 | 27 | fun joinByBullet(vararg str: String?) = 28 | str.filterNot { 29 | it.isNullOrEmpty() 30 | }.joinToString(separator = " • ") 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/viewmodels/AccountViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.viewmodels 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import com.zionhuang.innertube.YouTube 6 | import com.zionhuang.innertube.models.PlaylistItem 7 | import com.zionhuang.music.utils.reportException 8 | import dagger.hilt.android.lifecycle.HiltViewModel 9 | import kotlinx.coroutines.flow.MutableStateFlow 10 | import kotlinx.coroutines.launch 11 | import javax.inject.Inject 12 | 13 | @HiltViewModel 14 | class AccountViewModel @Inject constructor() : ViewModel() { 15 | val playlists = MutableStateFlow?>(null) 16 | 17 | init { 18 | viewModelScope.launch { 19 | YouTube.likedPlaylists().onSuccess { 20 | playlists.value = it 21 | }.onFailure { 22 | reportException(it) 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/viewmodels/ArtistViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.viewmodels 2 | 3 | import androidx.compose.runtime.getValue 4 | import androidx.compose.runtime.mutableStateOf 5 | import androidx.compose.runtime.setValue 6 | import androidx.lifecycle.SavedStateHandle 7 | import androidx.lifecycle.ViewModel 8 | import androidx.lifecycle.viewModelScope 9 | import com.zionhuang.innertube.YouTube 10 | import com.zionhuang.innertube.pages.ArtistPage 11 | import com.zionhuang.music.db.MusicDatabase 12 | import com.zionhuang.music.utils.reportException 13 | import dagger.hilt.android.lifecycle.HiltViewModel 14 | import kotlinx.coroutines.flow.SharingStarted 15 | import kotlinx.coroutines.flow.stateIn 16 | import kotlinx.coroutines.launch 17 | import javax.inject.Inject 18 | 19 | @HiltViewModel 20 | class ArtistViewModel @Inject constructor( 21 | database: MusicDatabase, 22 | savedStateHandle: SavedStateHandle, 23 | ) : ViewModel() { 24 | val artistId = savedStateHandle.get("artistId")!! 25 | var artistPage by mutableStateOf(null) 26 | val libraryArtist = database.artist(artistId) 27 | .stateIn(viewModelScope, SharingStarted.Lazily, null) 28 | val librarySongs = database.artistSongsPreview(artistId) 29 | .stateIn(viewModelScope, SharingStarted.Lazily, emptyList()) 30 | 31 | init { 32 | viewModelScope.launch { 33 | YouTube.artist(artistId) 34 | .onSuccess { 35 | artistPage = it 36 | }.onFailure { 37 | reportException(it) 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/viewmodels/MoodAndGenresViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.viewmodels 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import com.zionhuang.innertube.YouTube 6 | import com.zionhuang.innertube.pages.MoodAndGenres 7 | import com.zionhuang.music.utils.reportException 8 | import dagger.hilt.android.lifecycle.HiltViewModel 9 | import kotlinx.coroutines.flow.MutableStateFlow 10 | import kotlinx.coroutines.launch 11 | import javax.inject.Inject 12 | 13 | @HiltViewModel 14 | class MoodAndGenresViewModel @Inject constructor() : ViewModel() { 15 | val moodAndGenres = MutableStateFlow?>(null) 16 | 17 | init { 18 | viewModelScope.launch { 19 | YouTube.moodAndGenres().onSuccess { 20 | moodAndGenres.value = it 21 | }.onFailure { 22 | reportException(it) 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/viewmodels/OnlinePlaylistViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.viewmodels 2 | 3 | import androidx.lifecycle.SavedStateHandle 4 | import androidx.lifecycle.ViewModel 5 | import androidx.lifecycle.viewModelScope 6 | import com.zionhuang.innertube.YouTube 7 | import com.zionhuang.innertube.models.PlaylistItem 8 | import com.zionhuang.innertube.models.SongItem 9 | import com.zionhuang.innertube.utils.completed 10 | import com.zionhuang.music.utils.reportException 11 | import dagger.hilt.android.lifecycle.HiltViewModel 12 | import kotlinx.coroutines.Dispatchers 13 | import kotlinx.coroutines.flow.MutableStateFlow 14 | import kotlinx.coroutines.launch 15 | import javax.inject.Inject 16 | 17 | @HiltViewModel 18 | class OnlinePlaylistViewModel @Inject constructor( 19 | savedStateHandle: SavedStateHandle, 20 | ) : ViewModel() { 21 | private val playlistId = savedStateHandle.get("playlistId")!! 22 | 23 | val playlist = MutableStateFlow(null) 24 | val playlistSongs = MutableStateFlow>(emptyList()) 25 | 26 | init { 27 | viewModelScope.launch(Dispatchers.IO) { 28 | YouTube.playlist(playlistId).completed() 29 | .onSuccess { playlistPage -> 30 | playlist.value = playlistPage.playlist 31 | playlistSongs.value = playlistPage.songs 32 | }.onFailure { 33 | reportException(it) 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/com/zionhuang/music/viewmodels/YouTubeBrowseViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.music.viewmodels 2 | 3 | import androidx.lifecycle.SavedStateHandle 4 | import androidx.lifecycle.ViewModel 5 | import androidx.lifecycle.viewModelScope 6 | import com.zionhuang.innertube.YouTube 7 | import com.zionhuang.innertube.pages.BrowseResult 8 | import com.zionhuang.music.utils.reportException 9 | import dagger.hilt.android.lifecycle.HiltViewModel 10 | import kotlinx.coroutines.flow.MutableStateFlow 11 | import kotlinx.coroutines.launch 12 | import javax.inject.Inject 13 | 14 | @HiltViewModel 15 | class YouTubeBrowseViewModel @Inject constructor( 16 | savedStateHandle: SavedStateHandle, 17 | ) : ViewModel() { 18 | private val browseId = savedStateHandle.get("browseId")!! 19 | private val params = savedStateHandle.get("params") 20 | 21 | val result = MutableStateFlow(null) 22 | 23 | init { 24 | viewModelScope.launch { 25 | YouTube.browse(browseId, params).onSuccess { 26 | result.value = it 27 | }.onFailure { 28 | reportException(it) 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_stat_onesignal_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/app/src/main/res/drawable-hdpi/ic_stat_onesignal_default.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/splash_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/app/src/main/res/drawable-hdpi/splash_image.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_stat_onesignal_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/app/src/main/res/drawable-mdpi/ic_stat_onesignal_default.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/splash_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/app/src/main/res/drawable-mdpi/splash_image.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_stat_onesignal_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/app/src/main/res/drawable-xhdpi/ic_stat_onesignal_default.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/splash_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/app/src/main/res/drawable-xhdpi/splash_image.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_stat_onesignal_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/app/src/main/res/drawable-xxhdpi/ic_stat_onesignal_default.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/splash_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/app/src/main/res/drawable-xxhdpi/splash_image.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_stat_onesignal_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/app/src/main/res/drawable-xxxhdpi/ic_stat_onesignal_default.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/splash_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/app/src/main/res/drawable-xxxhdpi/splash_image.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/add.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/album.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/app_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/app/src/main/res/drawable/app_logo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/arrow_back.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/arrow_downward.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/arrow_forward.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/arrow_top_left.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/arrow_upward.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/artist.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/backup.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bedtime.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bookmark.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bookmark_filled.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/cached.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/casino.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/clear_all.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/close.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/contrast.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/dark_mode.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/delete.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/discover_tune.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/download.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/drag_handle.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/edit.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/equalizer.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/error.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/expand_less.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/expand_more.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/explicit.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/favorite.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/favorite_border.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/github.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/graphic_eq.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/grid_view.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/history.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/home.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/app/src/main/res/drawable/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/info.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/input.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/language.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/library_add.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/library_add_check.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/library_music.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/list.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/location_on.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/lock.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/lock_open.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/lyrics.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/manage_search.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/mood.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/more_horiz.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/more_vert.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/music_note.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/navigate_next.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/offline.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/palette.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/pause.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/person.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/play.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/playlist_add.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/playlist_play.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/queue_music.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/radio.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/radio_button_checked.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/radio_button_unchecked.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/remove.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/repeat.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/repeat_on.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/repeat_one.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/repeat_one_on.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/replay.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/restore.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/search.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/security.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/share.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shortcut_albums.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shortcut_playlists.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shortcut_search.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shortcut_songs.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shuffle.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shuffle_on.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/skip_next.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/skip_previous.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/slow_motion_video.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/splash_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/app/src/main/res/drawable/splash_image.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/storage.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/sync.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/tab.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/translate.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/trending_up.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/tune.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/update.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/volume_up.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/font/amsterdam.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/app/src/main/res/font/amsterdam.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/arvo.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/app/src/main/res/font/arvo.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/bod.TTF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/app/src/main/res/font/bod.TTF -------------------------------------------------------------------------------- /app/src/main/res/font/edutas.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/app/src/main/res/font/edutas.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/kenia.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/app/src/main/res/font/kenia.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/oswald.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/app/src/main/res/font/oswald.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/raleway1.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/app/src/main/res/font/raleway1.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/raleway2.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/app/src/main/res/font/raleway2.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/roboto1.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/app/src/main/res/font/roboto1.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/roboto2.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/app/src/main/res/font/roboto2.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/teko.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/app/src/main/res/font/teko.ttf -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/app_name.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | YouMusic 4 | 107606197596 5 | -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFFFF 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/values/values.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | @drawable/play 5 | @drawable/pause 6 | @drawable/skip_previous 7 | @drawable/skip_next 8 | -------------------------------------------------------------------------------- /app/src/main/res/xml/automotive_app_desc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/xml/provider_paths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 9 | 12 | -------------------------------------------------------------------------------- /assets/buymeacoffee.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/assets/buymeacoffee.png -------------------------------------------------------------------------------- /assets/liberapay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/assets/liberapay.png -------------------------------------------------------------------------------- /crowdin.yml: -------------------------------------------------------------------------------- 1 | files: 2 | - source: /app/src/main/res/values/strings.xml 3 | translation: /app/src/main/res/values-%android_code%/strings.xml 4 | -------------------------------------------------------------------------------- /fastlane/metadata/android/app_banner_youmusic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/fastlane/metadata/android/app_banner_youmusic.jpg -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/10.txt: -------------------------------------------------------------------------------- 1 |
Improved 2 | 3 | * Support Android 13 themed icon (#157) 4 | 5 |
Fixed 6 | 7 | * Fix stream can't be played (#161) -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/11.txt: -------------------------------------------------------------------------------- 1 | In this version, we are using a new library: Innertube, which makes more powerful features possible! 2 | * Browse everything as in YouTube Music 3 | * New home page to browse suggestions and find new music releases 4 | * Search result summary tab 5 | * Loading stream is faster 6 | 7 | Credit: vfsfitvnm/ViMusic, tombulled/innertube, zerodytrash/YouTube-Internal-Clients 8 | 9 | ⚠️ Note: For users upgrading from <0.3.3, please select all songs in your library and do "Refetch" to get thumbnails shown. -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/12.txt: -------------------------------------------------------------------------------- 1 | * Start radio from library songs 2 | * Default open tab setting 3 | * Audio quality setting 4 | * Pause or clear search history 5 | * Backup and restore 6 | * Support SOCKS proxy -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/13.txt: -------------------------------------------------------------------------------- 1 | Music is now renamed to InnerTune! 2 | 3 | - Player redesign 4 | - Queue redesign 5 | - Cache songs 6 | - Stats for nerds 7 | - Persistent queue 8 | - Add built-in playlist (liked, downloaded) 9 | - Set cache limits 10 | - Sort songs by play time 11 | - Customize navigation tabs -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/14.txt: -------------------------------------------------------------------------------- 1 | - Lyrics 2 | - Android Auto support 3 | - Skip silence 4 | - Audio normalization 5 | - Export downloaded songs via SAF 6 | - Improve player view layout 7 | - Add wake lock for player 8 | - Option to hide buttons in player notification 9 | - Delete persistent queue when restoring database 10 | - Show itag in stats for nerds 11 | - Minor changes and fixes -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/15.txt: -------------------------------------------------------------------------------- 1 | Improvement 2 | 3 | - Add shuffle button in Android Auto 4 | 5 | Fixed 6 | 7 | - Fix apostrophe displayed as HTML entity in lyrics #401 8 | - Update YouTube search filter key #436 9 | - Fix #432 10 | 11 | Translation 12 | 13 | - Update Hungarian translation #407 #425 14 | - Add Indonesia translation #415 15 | - Update Simplified Chinese translation #416 16 | - Update Japanese translation #419 -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/16.txt: -------------------------------------------------------------------------------- 1 | - New app icon 2 | - Rewrite UI using Jetpack Compose 3 | - Better UI/UX 4 | - Dynamic theme 5 | - Redesigned home screen 6 | - Improved download experience 7 | - New lyrics source: YouTube subtitle 8 | - Show replay button when playing ended -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/17.txt: -------------------------------------------------------------------------------- 1 | - Login support 2 | - Mood & Genres 3 | - Better stats screen 4 | - Bookmark artists 5 | - Minor enhancement and bug fixes -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/18.txt: -------------------------------------------------------------------------------- 1 | - Improve library design 2 | - Lyrics translator (full version only) 3 | - Minor enhancement and bug fixes -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/19.txt: -------------------------------------------------------------------------------- 1 | - Better UI 2 | - Grid layout for albums and playlists 3 | - Minor enhancement and bug fixes -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/5.txt: -------------------------------------------------------------------------------- 1 | * App updater (Preview) 2 | * Share button function in bottom control fragment 3 | * Open YouTube urls by this app #11 4 | * You can now search for YouTube Music albums 5 | * Remove download of a song 6 | * Show channel or playlist name in search fragment title 7 | * Scrollable search filter chip group 8 | * Minor fixes -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/6.txt: -------------------------------------------------------------------------------- 1 |
Fixed 2 | 3 | * Fix YouTube not playing any streams #27, TeamNewPipe/NewPipe#8202 4 | * Fix YouTube age restricted videos being throttled TeamNewPipe/NewPipeExtractor#832 -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/7.txt: -------------------------------------------------------------------------------- 1 | * Material You #25 2 | * Dark mode settings 3 | * Show an icon indicating if a song is in your library in search fragment 4 | * Make search filter bar fixed 5 | * Move song downloaded icon to second line 6 | * Smoother transition animation 7 | * Merge artists with same name (#50) 8 | * Minor fixes -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/8.txt: -------------------------------------------------------------------------------- 1 |
Fixed 2 | 3 | * Fix main content resize animation 4 | * Fix dark theme malfunction #69 5 | * Fix renaming an artist can lead to lose songs #75 6 | 7 |
Translation updates 8 | 9 | * Finnish (by @teemue) #67 10 | * Italian (by @airon90) #71 11 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/9.txt: -------------------------------------------------------------------------------- 1 |
Fixed 2 | 3 | * Fix stream can't be played (#148) 4 | * Fix bottom navigation item isn't checked when clicked (#12) 5 | 6 |
Translation updates 7 | 8 | * Korean (by @dongsu8142) #107 9 | * Spanish (by @DD21S) #120 10 | * Japanese (by @HiSubway) #138 11 | * Swedish (by @Itroublve) #143 -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/full_description.txt: -------------------------------------------------------------------------------- 1 | With this app, you're like getting a free music streaming service. You can listen to music from YouTube Music and build your own library. What's more, songs can be downloaded for offline playback. You can also create playlists to organize your songs. The aim of YouMusic is to enable everyone to listen to music at no cost by an easy-to-use, practical and ad-free application. 2 | 3 |
Note: 4 | 5 | The project is currently in an unstable stage. If you encounter bugs, please report by opening an issue on GitHub. 6 | 7 |
Features: 8 | 9 | - Play songs from YT/YT Music without ads 10 | - Background playback 11 | - Search songs, videos, albums, and playlists from YouTube Music 12 | - Library management 13 | - Cache and download songs for offline playback 14 | - Synchronized lyrics 15 | - Skip silence 16 | - Audio normalization 17 | - Dynamic theme 18 | - Localization 19 | - Android Auto support 20 | - Personalized quick picks 21 | - Material 3 -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/fastlane/metadata/android/en-US/images/icon.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/en1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/fastlane/metadata/android/en-US/images/phoneScreenshots/en1.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/en2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/fastlane/metadata/android/en-US/images/phoneScreenshots/en2.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/en3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/fastlane/metadata/android/en-US/images/phoneScreenshots/en3.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/en4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/fastlane/metadata/android/en-US/images/phoneScreenshots/en4.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/en5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/fastlane/metadata/android/en-US/images/phoneScreenshots/en5.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/en6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/fastlane/metadata/android/en-US/images/phoneScreenshots/en6.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/en7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/fastlane/metadata/android/en-US/images/phoneScreenshots/en7.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/en8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/fastlane/metadata/android/en-US/images/phoneScreenshots/en8.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/short_description.txt: -------------------------------------------------------------------------------- 1 | A Material 3 YouTube Music client for Android -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/title.txt: -------------------------------------------------------------------------------- 1 | InnerTune -------------------------------------------------------------------------------- /fastlane/metadata/android/es/full_description.txt: -------------------------------------------------------------------------------- 1 | Un cliente de YouTube Music con Material 3 para Android 2 | 3 |
Características: 4 | 5 | - Reproduce canciones de YT/YT Music sin anuncios 6 | - Reproducción en segundo plano 7 | - Busca canciones, vídeos, álbumes y listas de reproducción de YouTube Music 8 | - Gestión de biblioteca 9 | - Guarda en caché y descarga canciones para reproducirlas sin conexión 10 | - Letras sincronizadas 11 | - Saltar silencio 12 | - Normalización de audio 13 | - Tema dinámico 14 | - Localización 15 | - Compatibilidad con Android Auto 16 | - Selecciones rápidas personalizadas 17 | - Material 3 -------------------------------------------------------------------------------- /fastlane/metadata/android/es/short_description.txt: -------------------------------------------------------------------------------- 1 | Un cliente de YouTube Music con Material 3 para Android -------------------------------------------------------------------------------- /fastlane/metadata/android/it/full_description.txt: -------------------------------------------------------------------------------- 1 | Con quest'app, avrai un servizio di streaming musicale gratuito. Puoi ascoltare musica da YouTube Music e crearti la tua libreria musicale personale. Inoltre, i brani possono essere scaricati per un ascolto offline. Puoi anche creare delle playlist per organizzare la tua musica. L'obiettivo di YouMusic è quello di dare la possibilità a chiunque di ascoltare musica senza costi e facilmente, in maniera pratica e priva di pubblicità. 2 | 3 |
Nota: 4 | 5 | Il progetto è attualmente in una fase instabile. Se trovi degli errori, per favore segnalali facendolo su GitHub. 6 | 7 |
Caratteristiche: 8 | 9 | - Ascolto dei brani da YT/YT Music senza pubblicità 10 | - Riproduzione in background 11 | - Ricerca dei brani, video, album e playlist da YouTube Music 12 | - Gestione della libreria 13 | - Cache e download dei brani per la riproduzione offline 14 | - Testi sincronizzati 15 | - Salto del silenzio 16 | - Normalizzazione dell'audio 17 | - Tema dinamico 18 | - Varie lingue disponibili 19 | - Supporto per Android Auto 20 | - Scelte rapide personalizzate 21 | - Material 3 22 | -------------------------------------------------------------------------------- /fastlane/metadata/android/it/short_description.txt: -------------------------------------------------------------------------------- 1 | Un client di YouTube Music in Material 3 per Android 2 | -------------------------------------------------------------------------------- /fastlane/metadata/android/it/title.txt: -------------------------------------------------------------------------------- 1 | InnerTune 2 | -------------------------------------------------------------------------------- /fastlane/metadata/android/ja/full_description.txt: -------------------------------------------------------------------------------- 1 | このアプリを使ったら、無料の音楽ストリーミングサービスを手に入れたようなものです。YouTube Musicの音楽を聴いたり、自分だけのライブラリを作り上げたりできます。さらには、音楽をダウンロードし、オフラインで再生することもできます。また、プレイリストを作って、曲を整理することも可能です。簡単で、実用的で、広告のないアプリで、誰もが無料で音楽を聴くことができるようにすることが、YouMusicの目的です。 2 | 3 | 4 |
注意: 5 | 6 | 現在、プロジェクトはまだ安定しない段階です。もし不具合が発生したら、GitHubからissueを報告してください。 7 | 8 |
機能: 9 | 10 | * 広告なしで音楽を再生 11 | * YouTube Musicのほぼすべてのページを閲覧 12 | * YouTube Musicから、曲、アルバム、動画、プレイリストを検索 13 | * YouTube Musicのリンクを開く 14 | * 曲、アルバム、動画、プレイリストをローカルデータベースに保存 15 | * 曲をダウンロードしてオフラインで再生 16 | * 曲のタイトルを編集 17 | * YouTube Musicのお気に入りプレイリストへのリンクを追加 18 | * Material Designに準拠したプレイヤー 19 | * ロック画面での再生 20 | * 通知パネルで音楽をコントロール 21 | * 次の/前の曲にスキップ 22 | * リピート/シャッフルモード 23 | * 再生中のキューを編集 24 | * カスタムテーマ 25 | * ダークモード 26 | * 多言語対応 27 | * プロキシ 28 | * バックアップとリストア -------------------------------------------------------------------------------- /fastlane/metadata/android/ja/short_description.txt: -------------------------------------------------------------------------------- 1 | YouTube Musicから音楽をストリーミング再生するMaterial Designの音楽プレイヤー -------------------------------------------------------------------------------- /fastlane/metadata/android/ko/full_description.txt: -------------------------------------------------------------------------------- 1 | Music은 YouTube/YouTube Music에서 정보를 검색하고 데이터를 스트리밍하기 위해 NewPipe Extractor를 사용합니다. 또한 음악 플레이어이므로 재생 목록을 직접 만들고 만든 아티스트별로 노래를 구성할 수 있습니다. Music의 목적은 사용하기 쉽고 실용적이며 광고 없이 모든 사람이 무료로 음악을 들을 수 있도록 하는 것입니다. 2 | 3 |
메모: 4 | 5 | 그 프로젝트는 현재 불안정한 단계에 있습니다. 버그가 발생하면 GitHub에서 이슈를 열어 보고해주세요. 6 | 7 |
특징: 8 | 9 | * 광고 없음 10 | * YouTube/YouTube Music에서 노래, 비디오, 재생 목록 및 채널 검색 11 | * YouTube에서 대기열의 마지막 5곡을 재생할 때 더 많은 노래를 자동으로 로드 12 | * 머티리얼 디자인 플레이어 13 | * 잠금 화면에서 재생 14 | * 알림의 미디어 컨트롤 15 | * 다음/이전 노래로 건너뛰기 16 | * 반복/셔플 모드 17 | * 지금 재생 중인 대기열 편집 18 | * YouTube/YouTube Music에서 노래 재생 및 저장 19 | * 오프라인 재생을 위한 음악 다운로드 20 | * 로컬 데이터베이스에 재생 목록 만들기 21 | * 노래 이름 및 노래 아티스트 편집 22 | -------------------------------------------------------------------------------- /fastlane/metadata/android/ko/short_description.txt: -------------------------------------------------------------------------------- 1 | YouTube/YouTube Music의 음악이 포함된 머티리얼 디자인 뮤직 플레이어 2 | -------------------------------------------------------------------------------- /fastlane/metadata/android/pa/full_description.txt: -------------------------------------------------------------------------------- 1 | ਇਸ ਐਪ ਦੇ ਨਾਲ, ਤੁਸੀਂ ਇੱਕ ਮੁਫਤ ਸੰਗੀਤ ਸਟ੍ਰੀਮਿੰਗ ਸੇਵਾ ਪ੍ਰਾਪਤ ਕਰਨ ਜਾ ਰਹੇ ਹੋ। ਤੁਸੀਂ ਯੂਟਿਊਬ ਮਿਊਜ਼ਕ ਤੋਂ ਸੰਗੀਤ ਸੁਣ ਸਕਦੇ ਹੋ ਅਤੇ ਆਪਣੀ ਖੁਦ ਦੀ ਲਾਇਬ੍ਰੇਰੀ ਬਣਾ ਸਕਦੇ ਹੋ। ਇਸ ਤੋਂ ਇਲਾਵਾ, ਆਫਲਾਈਨ ਪਲੇਬੈਕ ਲਈ ਗਾਣੇ ਡਾਊਨਲੋਡ ਕੀਤੇ ਜਾ ਸਕਦੇ ਹਨ। ਤੁਸੀਂ ਆਪਣੇ ਗੀਤਾਂ ਨੂੰ ਸੰਗਠਿਤ ਕਰਨ ਲਈ ਪਲੇਲਿਸਟਾਂ ਵੀ ਬਣਾ ਸਕਦੇ ਹੋ। ਇਨਰਟਿਊਨ ਦਾ ਉਦੇਸ਼ ਹਰ ਕਿਸੇ ਨੂੰ ਬਿਨਾਂ ਕਿਸੇ ਕੀਮਤ 'ਤੇ ਵਰਤੋਂ-ਵਿੱਚ-ਆਸਾਨ, ਵਿਹਾਰਕ ਅਤੇ ਵਿਗਿਆਪਨ-ਮੁਕਤ ਐਪਲੀਕੇਸ਼ਨ ਦੁਆਰਾ ਸੰਗੀਤ ਸੁਣਨ ਦੇ ਯੋਗ ਬਣਾਉਣਾ ਹੈ। 2 | 3 |
ਨੋਟ: 4 | 5 | ਪ੍ਰੋਜੈਕਟ ਇਸ ਸਮੇਂ ਇੱਕ ਅਸਥਿਰ ਪੜਾਅ ਵਿੱਚ ਹੈ। ਜੇਕਰ ਤੁਹਾਨੂੰ ਬੱਗ ਨਜ਼ਰ ਆਉਂਦੇ ਹਨ, ਤਾਂ ਕਿਰਪਾ ਕਰਕੇ ਗਿਟਹੱਬਉੱਪਰ ਕੋਈ ਸਮੱਸਿਆ ਖੋਲ੍ਹ ਕੇ ਰਿਪੋਰਟ ਕਰੋ। 6 | 7 |
ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ: 8 | 9 | * ਬਿਨਾਂ ਵਿਗਿਆਪਨਾਂ ਦੇ ਗੀਤ ਚਲਾਓ 10 | * ਔਫਲਾਈਨ ਪਲੇਬੈਕ ਲਈ ਸੰਗੀਤ ਡਾਊਨਲੋਡ ਕਰੋ 11 | * ਲੋਕਲ ਲਾਇਬ੍ਰੇਰੀ ਪ੍ਰਬੰਧ 12 | * ਗੀਤ ਕੈਸ਼ੇ ਕਰੋ 13 | * ਸਮਵਰਤੀ ਬੋਲ 14 | * ਆਡੀਓ ਨਾਰਮੇਲਾਈਜ਼ੇਸ਼ਨ 15 | * ਚੁੱਪ ਨੂੰ ਅੱਗੇ ਲੰਘਾਓ 16 | * ਬੈਕਅੱਪ ਅਤੇ ਰੀਸਟੋਰ 17 | * ਪ੍ਰੌਕਸੀ ਸਮਰਥਨ 18 | * ਐਂਡਰਾਇਡ ਆਟੋ ਦਾ ਸਮਰਥਨ -------------------------------------------------------------------------------- /fastlane/metadata/android/pa/short_description.txt: -------------------------------------------------------------------------------- 1 | ਇੱਕ ਮਟੀਰੀਅਲ ਡਿਜ਼ਾਈਨ ਵਾਲਾ ਯੂਟਿਊਬ ਮਿਊਜ਼ਕ ਕਲਾਈਂਟ -------------------------------------------------------------------------------- /fastlane/metadata/android/ru-RU/full_description.txt: -------------------------------------------------------------------------------- 1 | Клиент YouTube Music для Android в стиле Material 3 2 | 3 |
Особенности: 4 | 5 | - Воспроизведение песен с YT/YT Music без рекламы 6 | - Фоновое воспроизведение 7 | - Поиск песен, видео, альбомов и плейлистов в YouTube Music 8 | - Управление библиотекой 9 | - Кэширование и загрузка песен для офлайн-воспроизведения 10 | - Синхронизированный текст песен 11 | - Пропуск тишины 12 | - Нормализация аудио 13 | - Динамическая тема 14 | - Локализация 15 | - Поддержка Android Auto 16 | - Персонализированные быстрые выборки 17 | - Material 3 18 | -------------------------------------------------------------------------------- /fastlane/metadata/android/ru-RU/short_description.txt: -------------------------------------------------------------------------------- 1 | Клиент YouTube Music для Android в стиле Material 3 2 | -------------------------------------------------------------------------------- /fastlane/metadata/android/ru-RU/title.txt: -------------------------------------------------------------------------------- 1 | InnerTune 2 | -------------------------------------------------------------------------------- /fastlane/metadata/android/tr/full_description.txt: -------------------------------------------------------------------------------- 1 | Bu uygulamayla, ücretsiz bir müzik akışı hizmeti alıyor gibisiniz. YouTube Music'ten müzik dinleyebilir ve kendi kitaplığınızı oluşturabilirsiniz. Dahası, şarkılar çevrimdışı oynatmak için indirilebilir. Şarkılarınızı düzenlemek için çalma listeleri de oluşturabilirsiniz. YouMusic'in amacı, kullanımı kolay, pratik ve reklamsız bir uygulama ile herkesin ücretsiz müzik dinlemesini sağlamaktır. 2 | 3 |
Not: 4 | 5 | Proje şu anda istikrarsız bir aşamada. Hatalarla karşılaşırsanız, lütfen GitHub'da bir sorun açarak bildirin. 6 | 7 |
Özellikler: 8 | 9 | - YT/YT Müzik'teki şarkıları reklamsız dinleyin 10 | - Arka planda oynatma 11 | - YouTube Müzik'te şarkı, video, albüm ve çalma listesi arama 12 | - Kütüphane yönetimi 13 | - Çevrimdışı çalmak için şarkıları önbelleğe alma ve indirme 14 | - Senkronize şarkı sözleri 15 | - Sessizliği atlama 16 | - Ses normalleştirme 17 | - Dinamik tema 18 | - Yerelleştirme 19 | - Android Auto desteği 20 | - Kişiselleştirilmiş hızlı seçimler 21 | - Materyal 3 22 | -------------------------------------------------------------------------------- /fastlane/metadata/android/tr/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/fastlane/metadata/android/tr/images/icon.png -------------------------------------------------------------------------------- /fastlane/metadata/android/tr/images/phoneScreenshots/tr1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/fastlane/metadata/android/tr/images/phoneScreenshots/tr1.png -------------------------------------------------------------------------------- /fastlane/metadata/android/tr/images/phoneScreenshots/tr2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/fastlane/metadata/android/tr/images/phoneScreenshots/tr2.png -------------------------------------------------------------------------------- /fastlane/metadata/android/tr/images/phoneScreenshots/tr3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/fastlane/metadata/android/tr/images/phoneScreenshots/tr3.png -------------------------------------------------------------------------------- /fastlane/metadata/android/tr/images/phoneScreenshots/tr4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/fastlane/metadata/android/tr/images/phoneScreenshots/tr4.png -------------------------------------------------------------------------------- /fastlane/metadata/android/tr/images/phoneScreenshots/tr5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/fastlane/metadata/android/tr/images/phoneScreenshots/tr5.png -------------------------------------------------------------------------------- /fastlane/metadata/android/tr/images/phoneScreenshots/tr6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/fastlane/metadata/android/tr/images/phoneScreenshots/tr6.png -------------------------------------------------------------------------------- /fastlane/metadata/android/tr/images/phoneScreenshots/tr7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/fastlane/metadata/android/tr/images/phoneScreenshots/tr7.png -------------------------------------------------------------------------------- /fastlane/metadata/android/tr/images/phoneScreenshots/tr8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/fastlane/metadata/android/tr/images/phoneScreenshots/tr8.png -------------------------------------------------------------------------------- /fastlane/metadata/android/tr/short_description.txt: -------------------------------------------------------------------------------- 1 | Materyal tasarımlı bir YouTube Müzik istemcisi -------------------------------------------------------------------------------- /fastlane/metadata/android/uk-UA/full_description.txt: -------------------------------------------------------------------------------- 1 | Клієнт YouTube Music для Android у стилі Material 3 2 | 3 | Особливості: 4 | 5 | - Відтворення пісень з YT/YT Music без реклами 6 | - Фонове відтворення 7 | - Пошук пісень, відео, альбомів та плейлистів в YouTube Music 8 | - Керування бібліотекою 9 | - Кешування та завантаження пісень для офлайн-відтворення 10 | - Синхронізований текст пісень 11 | - Пропуск тиші 12 | - Нормалізація аудіо 13 | - Динамічна тема 14 | - Локалізація 15 | - Підтримка Android Auto 16 | - Персоналізовані швидкі вибірки 17 | - Material 3 18 | -------------------------------------------------------------------------------- /fastlane/metadata/android/uk-UA/short_description.txt: -------------------------------------------------------------------------------- 1 | Клієнт YouTube Music для Android у стилі Material 3 2 | -------------------------------------------------------------------------------- /fastlane/metadata/android/uk-UA/title.txt: -------------------------------------------------------------------------------- 1 | InnerTune 2 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | ## For more details on how to configure your build environment visit 2 | # http://www.gradle.org/docs/current/userguide/build_environment.html 3 | # 4 | # Specifies the JVM arguments used for the daemon process. 5 | # The setting is particularly useful for tweaking memory settings. 6 | # Default value: -Xmx1024m -XX:MaxPermSize=256m 7 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 8 | # 9 | # When configured, Gradle will run in incubating parallel mode. 10 | # This option should only be used with decoupled projects. More details, visit 11 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 12 | # org.gradle.parallel=true 13 | #Sat Nov 19 15:59:34 CST 2022 14 | 15 | org.gradle.jvmargs=-Xmx2048M -Dkotlin.daemon.jvm.options\="-Xmx2048M" 16 | android.useAndroidX=true 17 | android.enableJetifier=true 18 | org.gradle.unsafe.configuration-cache=true 19 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamYouDown/YouMusic/6cf58be04ea6d7f70085ff8569e3d59ca18d3356/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Oct 04 14:57:57 CST 2022 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /innertube/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /innertube/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") 3 | @Suppress("DSL_SCOPE_VIOLATION") 4 | alias(libs.plugins.kotlin.serialization) 5 | } 6 | 7 | kotlin { 8 | jvmToolchain(11) 9 | } 10 | 11 | dependencies { 12 | implementation(libs.ktor.client.core) 13 | implementation(libs.ktor.client.okhttp) 14 | implementation(libs.ktor.client.content.negotiation) 15 | implementation(libs.ktor.serialization.json) 16 | implementation(libs.ktor.client.encoding) 17 | implementation(libs.brotli) 18 | testImplementation(libs.junit) 19 | } -------------------------------------------------------------------------------- /innertube/src/main/java/com/zionhuang/innertube/encoder/BrotliEncoder.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.innertube.encoder 2 | 3 | import io.ktor.client.plugins.compression.* 4 | import io.ktor.utils.io.* 5 | import io.ktor.utils.io.jvm.javaio.* 6 | import kotlinx.coroutines.CoroutineScope 7 | import org.brotli.dec.BrotliInputStream 8 | 9 | object BrotliEncoder : ContentEncoder { 10 | override val name: String = "br" 11 | 12 | override fun CoroutineScope.decode(source: ByteReadChannel): ByteReadChannel = 13 | BrotliInputStream(source.toInputStream()).toByteReadChannel() 14 | 15 | override fun CoroutineScope.encode(source: ByteReadChannel): ByteReadChannel = 16 | throw UnsupportedOperationException("Encode not implemented by the library yet.") 17 | } 18 | 19 | fun ContentEncoding.Config.brotli(quality: Float? = null) { 20 | customEncoder(BrotliEncoder, quality) 21 | } -------------------------------------------------------------------------------- /innertube/src/main/java/com/zionhuang/innertube/models/AccountInfo.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.innertube.models 2 | 3 | data class AccountInfo( 4 | val name: String, 5 | val email: String?, 6 | val channelHandle: String?, 7 | ) 8 | -------------------------------------------------------------------------------- /innertube/src/main/java/com/zionhuang/innertube/models/AutomixPreviewVideoRenderer.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.innertube.models 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class AutomixPreviewVideoRenderer( 7 | val content: Content, 8 | ) { 9 | @Serializable 10 | data class Content( 11 | val automixPlaylistVideoRenderer: AutomixPlaylistVideoRenderer, 12 | ) { 13 | @Serializable 14 | data class AutomixPlaylistVideoRenderer( 15 | val navigationEndpoint: NavigationEndpoint, 16 | ) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /innertube/src/main/java/com/zionhuang/innertube/models/Badges.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.innertube.models 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class Badges( 7 | val musicInlineBadgeRenderer: MusicInlineBadgeRenderer, 8 | ) { 9 | @Serializable 10 | data class MusicInlineBadgeRenderer( 11 | val icon: Icon, 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /innertube/src/main/java/com/zionhuang/innertube/models/Button.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.innertube.models 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class Button( 7 | val buttonRenderer: ButtonRenderer, 8 | ) { 9 | @Serializable 10 | data class ButtonRenderer( 11 | val text: Runs, 12 | val navigationEndpoint: NavigationEndpoint?, 13 | val command: NavigationEndpoint?, 14 | val icon: Icon?, 15 | ) 16 | } -------------------------------------------------------------------------------- /innertube/src/main/java/com/zionhuang/innertube/models/Context.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.innertube.models 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class Context( 7 | val client: Client, 8 | val thirdParty: ThirdParty? = null, 9 | ) { 10 | @Serializable 11 | data class Client( 12 | val clientName: String, 13 | val clientVersion: String, 14 | val gl: String, 15 | val hl: String, 16 | val visitorData: String?, 17 | ) 18 | 19 | @Serializable 20 | data class ThirdParty( 21 | val embedUrl: String, 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /innertube/src/main/java/com/zionhuang/innertube/models/Continuation.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.innertube.models 2 | 3 | import kotlinx.serialization.ExperimentalSerializationApi 4 | import kotlinx.serialization.Serializable 5 | import kotlinx.serialization.json.JsonNames 6 | 7 | @OptIn(ExperimentalSerializationApi::class) 8 | @Serializable 9 | data class Continuation( 10 | @JsonNames("nextContinuationData", "nextRadioContinuationData") 11 | val nextContinuationData: NextContinuationData?, 12 | ) { 13 | @Serializable 14 | data class NextContinuationData( 15 | val continuation: String, 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /innertube/src/main/java/com/zionhuang/innertube/models/GridRenderer.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.innertube.models 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class GridRenderer( 7 | val header: Header?, 8 | val items: List, 9 | ) { 10 | @Serializable 11 | data class Header( 12 | val gridHeaderRenderer: GridHeaderRenderer, 13 | ) { 14 | @Serializable 15 | data class GridHeaderRenderer( 16 | val title: Runs, 17 | ) 18 | } 19 | 20 | @Serializable 21 | data class Item( 22 | val musicNavigationButtonRenderer: MusicNavigationButtonRenderer?, 23 | val musicTwoRowItemRenderer: MusicTwoRowItemRenderer?, 24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /innertube/src/main/java/com/zionhuang/innertube/models/Icon.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.innertube.models 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class Icon( 7 | val iconType: String, 8 | ) -------------------------------------------------------------------------------- /innertube/src/main/java/com/zionhuang/innertube/models/Menu.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.innertube.models 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class Menu( 7 | val menuRenderer: MenuRenderer, 8 | ) { 9 | @Serializable 10 | data class MenuRenderer( 11 | val items: List, 12 | val topLevelButtons: List?, 13 | ) { 14 | @Serializable 15 | data class Item( 16 | val menuNavigationItemRenderer: MenuNavigationItemRenderer?, 17 | val menuServiceItemRenderer: MenuServiceItemRenderer?, 18 | ) { 19 | @Serializable 20 | data class MenuNavigationItemRenderer( 21 | val text: Runs, 22 | val icon: Icon, 23 | val navigationEndpoint: NavigationEndpoint, 24 | ) 25 | 26 | @Serializable 27 | data class MenuServiceItemRenderer( 28 | val text: Runs, 29 | val icon: Icon, 30 | val serviceEndpoint: NavigationEndpoint, 31 | ) 32 | } 33 | 34 | @Serializable 35 | data class TopLevelButton( 36 | val buttonRenderer: ButtonRenderer?, 37 | ) { 38 | @Serializable 39 | data class ButtonRenderer( 40 | val icon: Icon, 41 | val navigationEndpoint: NavigationEndpoint, 42 | ) 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /innertube/src/main/java/com/zionhuang/innertube/models/MusicCardShelfRenderer.kt: -------------------------------------------------------------------------------- 1 | package com.zionhuang.innertube.models 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class MusicCardShelfRenderer( 7 | val title: Runs, 8 | val subtitle: Runs, 9 | val thumbnail: ThumbnailRenderer, 10 | val header: Header, 11 | val contents: List?, 12 | val buttons: List