From 1dabd07de41f077d34f7fff938a160b8d40740ed Mon Sep 17 00:00:00 2001 From: Bnyro Date: Sat, 1 Mar 2025 17:49:49 +0100 Subject: [PATCH 1/3] refactor: decouple and abstract auth from media service logic --- .../com/github/libretube/api/ExternalApi.kt | 4 + .../libretube/api/MediaServiceRepository.kt | 35 +++++ .../java/com/github/libretube/api/PipedApi.kt | 119 ---------------- .../com/github/libretube/api/PipedAuthApi.kt | 128 ++++++++++++++++++ .../api/PipedMediaServiceRepository.kt | 75 ++++++++++ .../github/libretube/api/PlaylistsHelper.kt | 2 +- .../github/libretube/api/RetrofitInstance.kt | 53 +++----- .../github/libretube/api/StreamsExtractor.kt | 2 +- .../github/libretube/helpers/ProxyHelper.kt | 8 +- .../repo/LocalPlaylistsRepository.kt | 6 +- .../libretube/services/OnlinePlayerService.kt | 5 +- .../PlaylistDownloadEnqueueService.kt | 6 +- .../ui/dialogs/SubmitDeArrowDialog.kt | 3 +- .../ui/dialogs/SubmitSegmentDialog.kt | 3 +- .../ui/fragments/ChannelContentFragment.kt | 8 +- .../libretube/ui/fragments/ChannelFragment.kt | 6 +- .../ui/fragments/PlaylistFragment.kt | 4 +- .../ui/fragments/SearchSuggestionsFragment.kt | 4 +- .../libretube/ui/models/HomeViewModel.kt | 4 +- .../libretube/ui/models/TrendsViewModel.kt | 4 +- .../libretube/ui/models/WelcomeViewModel.kt | 2 +- .../ui/models/sources/CommentPagingSource.kt | 6 +- .../sources/CommentRepliesPagingSource.kt | 4 +- .../ui/models/sources/SearchPagingSource.kt | 6 +- .../ui/preferences/InstanceSettings.kt | 9 +- .../ui/sheets/ChannelOptionsBottomSheet.kt | 6 +- .../ui/sheets/PlaylistOptionsBottomSheet.kt | 4 +- .../com/github/libretube/util/DeArrowUtil.kt | 4 +- .../com/github/libretube/util/PlayingQueue.kt | 7 +- 29 files changed, 320 insertions(+), 207 deletions(-) create mode 100644 app/src/main/java/com/github/libretube/api/MediaServiceRepository.kt create mode 100644 app/src/main/java/com/github/libretube/api/PipedAuthApi.kt create mode 100644 app/src/main/java/com/github/libretube/api/PipedMediaServiceRepository.kt diff --git a/app/src/main/java/com/github/libretube/api/ExternalApi.kt b/app/src/main/java/com/github/libretube/api/ExternalApi.kt index b8107dd46..b1958050c 100644 --- a/app/src/main/java/com/github/libretube/api/ExternalApi.kt +++ b/app/src/main/java/com/github/libretube/api/ExternalApi.kt @@ -1,6 +1,7 @@ package com.github.libretube.api import com.github.libretube.api.obj.DeArrowBody +import com.github.libretube.api.obj.PipedConfig import com.github.libretube.api.obj.PipedInstance import com.github.libretube.api.obj.SubmitSegmentResponse import com.github.libretube.api.obj.VoteInfo @@ -20,6 +21,9 @@ interface ExternalApi { @GET suspend fun getInstances(@Url url: String): List + @GET("config") + suspend fun getInstanceConfig(@Url url: String): PipedConfig + // fetch latest version info @GET(GITHUB_API_URL) suspend fun getLatestRelease(): UpdateInfo diff --git a/app/src/main/java/com/github/libretube/api/MediaServiceRepository.kt b/app/src/main/java/com/github/libretube/api/MediaServiceRepository.kt new file mode 100644 index 000000000..bbbbdc753 --- /dev/null +++ b/app/src/main/java/com/github/libretube/api/MediaServiceRepository.kt @@ -0,0 +1,35 @@ +package com.github.libretube.api + +import com.github.libretube.api.obj.Channel +import com.github.libretube.api.obj.ChannelTabResponse +import com.github.libretube.api.obj.CommentsPage +import com.github.libretube.api.obj.DeArrowContent +import com.github.libretube.api.obj.Playlist +import com.github.libretube.api.obj.SearchResult +import com.github.libretube.api.obj.SegmentData +import com.github.libretube.api.obj.StreamItem +import com.github.libretube.api.obj.Streams + +interface MediaServiceRepository { + suspend fun getTrending(region: String): List + suspend fun getStreams(videoId: String): Streams + suspend fun getComments(videoId: String): CommentsPage + suspend fun getSegments(videoId: String, category: String, actionType: String? = null): SegmentData + suspend fun getDeArrowContent(videoIds: String): Map + suspend fun getCommentsNextPage(videoId: String, nextPage: String): CommentsPage + suspend fun getSearchResults(searchQuery: String, filter: String): SearchResult + suspend fun getSearchResultsNextPage(searchQuery: String, filter: String, nextPage: String): SearchResult + suspend fun getSuggestions(query: String): List + suspend fun getChannel(channelId: String): Channel + suspend fun getChannelTab(data: String, nextPage: String? = null): ChannelTabResponse + suspend fun getChannelByName(channelName: String): Channel + suspend fun getChannelNextPage(channelId: String, nextPage: String): Channel + suspend fun getPlaylist(playlistId: String): Playlist + suspend fun getPlaylistNextPage(playlistId: String, nextPage: String): Playlist + + companion object { + val instance by lazy { + PipedMediaServiceRepository() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/libretube/api/PipedApi.kt b/app/src/main/java/com/github/libretube/api/PipedApi.kt index b185b4fe2..4542b3f07 100644 --- a/app/src/main/java/com/github/libretube/api/PipedApi.kt +++ b/app/src/main/java/com/github/libretube/api/PipedApi.kt @@ -4,33 +4,16 @@ import com.github.libretube.api.obj.Channel import com.github.libretube.api.obj.ChannelTabResponse import com.github.libretube.api.obj.CommentsPage import com.github.libretube.api.obj.DeArrowContent -import com.github.libretube.api.obj.DeleteUserRequest -import com.github.libretube.api.obj.EditPlaylistBody -import com.github.libretube.api.obj.Login -import com.github.libretube.api.obj.Message -import com.github.libretube.api.obj.PipedConfig import com.github.libretube.api.obj.Playlist -import com.github.libretube.api.obj.Playlists import com.github.libretube.api.obj.SearchResult import com.github.libretube.api.obj.SegmentData import com.github.libretube.api.obj.StreamItem import com.github.libretube.api.obj.Streams -import com.github.libretube.api.obj.Subscribe -import com.github.libretube.api.obj.Subscribed -import com.github.libretube.api.obj.Subscription -import com.github.libretube.api.obj.Token -import retrofit2.http.Body import retrofit2.http.GET -import retrofit2.http.Header -import retrofit2.http.PATCH -import retrofit2.http.POST import retrofit2.http.Path import retrofit2.http.Query interface PipedApi { - @GET("config") - suspend fun getConfig(): PipedConfig - @GET("trending") suspend fun getTrending(@Query("region") region: String): List @@ -98,106 +81,4 @@ interface PipedApi { @Path("playlistId") playlistId: String, @Query("nextpage") nextPage: String ): Playlist - - @POST("login") - suspend fun login(@Body login: Login): Token - - @POST("register") - suspend fun register(@Body login: Login): Token - - @POST("user/delete") - suspend fun deleteAccount( - @Header("Authorization") token: String, - @Body password: DeleteUserRequest - ) - - @GET("feed") - suspend fun getFeed(@Query("authToken") token: String?): List - - @GET("feed/unauthenticated") - suspend fun getUnauthenticatedFeed(@Query("channels") channels: String): List - - @POST("feed/unauthenticated") - suspend fun getUnauthenticatedFeed(@Body channels: List): List - - @GET("subscribed") - suspend fun isSubscribed( - @Query("channelId") channelId: String, - @Header("Authorization") token: String - ): Subscribed - - @GET("subscriptions") - suspend fun subscriptions(@Header("Authorization") token: String): List - - @GET("subscriptions/unauthenticated") - suspend fun unauthenticatedSubscriptions( - @Query("channels") channels: String - ): List - - @POST("subscriptions/unauthenticated") - suspend fun unauthenticatedSubscriptions(@Body channels: List): List - - @POST("subscribe") - suspend fun subscribe( - @Header("Authorization") token: String, - @Body subscribe: Subscribe - ): Message - - @POST("unsubscribe") - suspend fun unsubscribe( - @Header("Authorization") token: String, - @Body subscribe: Subscribe - ): Message - - @POST("import") - suspend fun importSubscriptions( - @Query("override") override: Boolean, - @Header("Authorization") token: String, - @Body channels: List - ): Message - - @POST("import/playlist") - suspend fun clonePlaylist( - @Header("Authorization") token: String, - @Body editPlaylistBody: EditPlaylistBody - ): EditPlaylistBody - - @GET("user/playlists") - suspend fun getUserPlaylists(@Header("Authorization") token: String): List - - @POST("user/playlists/rename") - suspend fun renamePlaylist( - @Header("Authorization") token: String, - @Body editPlaylistBody: EditPlaylistBody - ): Message - - @PATCH("user/playlists/description") - suspend fun changePlaylistDescription( - @Header("Authorization") token: String, - @Body editPlaylistBody: EditPlaylistBody - ): Message - - @POST("user/playlists/delete") - suspend fun deletePlaylist( - @Header("Authorization") token: String, - @Body editPlaylistBody: EditPlaylistBody - ): Message - - @POST("user/playlists/create") - suspend fun createPlaylist( - @Header("Authorization") token: String, - @Body name: Playlists - ): EditPlaylistBody - - @POST("user/playlists/add") - suspend fun addToPlaylist( - @Header("Authorization") token: String, - @Body editPlaylistBody: EditPlaylistBody - ): Message - - @POST("user/playlists/remove") - suspend fun removeFromPlaylist( - @Header("Authorization") token: String, - @Body editPlaylistBody: EditPlaylistBody - ): Message } diff --git a/app/src/main/java/com/github/libretube/api/PipedAuthApi.kt b/app/src/main/java/com/github/libretube/api/PipedAuthApi.kt new file mode 100644 index 000000000..d6cacbfc2 --- /dev/null +++ b/app/src/main/java/com/github/libretube/api/PipedAuthApi.kt @@ -0,0 +1,128 @@ +package com.github.libretube.api + +import com.github.libretube.api.obj.DeleteUserRequest +import com.github.libretube.api.obj.EditPlaylistBody +import com.github.libretube.api.obj.Login +import com.github.libretube.api.obj.Message +import com.github.libretube.api.obj.Playlist +import com.github.libretube.api.obj.Playlists +import com.github.libretube.api.obj.StreamItem +import com.github.libretube.api.obj.Subscribe +import com.github.libretube.api.obj.Subscribed +import com.github.libretube.api.obj.Subscription +import com.github.libretube.api.obj.Token +import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.Header +import retrofit2.http.PATCH +import retrofit2.http.POST +import retrofit2.http.Path +import retrofit2.http.Query + +interface PipedAuthApi { + + @POST("login") + suspend fun login(@Body login: Login): Token + + @POST("register") + suspend fun register(@Body login: Login): Token + + @POST("user/delete") + suspend fun deleteAccount( + @Header("Authorization") token: String, + @Body password: DeleteUserRequest + ) + + @GET("feed") + suspend fun getFeed(@Query("authToken") token: String?): List + + @GET("feed/unauthenticated") + suspend fun getUnauthenticatedFeed(@Query("channels") channels: String): List + + @POST("feed/unauthenticated") + suspend fun getUnauthenticatedFeed(@Body channels: List): List + + @GET("subscribed") + suspend fun isSubscribed( + @Query("channelId") channelId: String, + @Header("Authorization") token: String + ): Subscribed + + @GET("subscriptions") + suspend fun subscriptions(@Header("Authorization") token: String): List + + @GET("subscriptions/unauthenticated") + suspend fun unauthenticatedSubscriptions( + @Query("channels") channels: String + ): List + + @POST("subscriptions/unauthenticated") + suspend fun unauthenticatedSubscriptions(@Body channels: List): List + + @POST("subscribe") + suspend fun subscribe( + @Header("Authorization") token: String, + @Body subscribe: Subscribe + ): Message + + @POST("unsubscribe") + suspend fun unsubscribe( + @Header("Authorization") token: String, + @Body subscribe: Subscribe + ): Message + + @POST("import") + suspend fun importSubscriptions( + @Query("override") override: Boolean, + @Header("Authorization") token: String, + @Body channels: List + ): Message + + @POST("import/playlist") + suspend fun clonePlaylist( + @Header("Authorization") token: String, + @Body editPlaylistBody: EditPlaylistBody + ): EditPlaylistBody + + @GET("user/playlists") + suspend fun getUserPlaylists(@Header("Authorization") token: String): List + + @POST("user/playlists/rename") + suspend fun renamePlaylist( + @Header("Authorization") token: String, + @Body editPlaylistBody: EditPlaylistBody + ): Message + + @PATCH("user/playlists/description") + suspend fun changePlaylistDescription( + @Header("Authorization") token: String, + @Body editPlaylistBody: EditPlaylistBody + ): Message + + @POST("user/playlists/delete") + suspend fun deletePlaylist( + @Header("Authorization") token: String, + @Body editPlaylistBody: EditPlaylistBody + ): Message + + @POST("user/playlists/create") + suspend fun createPlaylist( + @Header("Authorization") token: String, + @Body name: Playlists + ): EditPlaylistBody + + @POST("user/playlists/add") + suspend fun addToPlaylist( + @Header("Authorization") token: String, + @Body editPlaylistBody: EditPlaylistBody + ): Message + + @POST("user/playlists/remove") + suspend fun removeFromPlaylist( + @Header("Authorization") token: String, + @Body editPlaylistBody: EditPlaylistBody + ): Message + + @GET("playlists/{playlistId}") + suspend fun getPlaylist(@Path("playlistId") playlistId: String): Playlist +} \ No newline at end of file diff --git a/app/src/main/java/com/github/libretube/api/PipedMediaServiceRepository.kt b/app/src/main/java/com/github/libretube/api/PipedMediaServiceRepository.kt new file mode 100644 index 000000000..a7aef9d72 --- /dev/null +++ b/app/src/main/java/com/github/libretube/api/PipedMediaServiceRepository.kt @@ -0,0 +1,75 @@ +package com.github.libretube.api + +import com.github.libretube.api.RetrofitInstance.PIPED_API_URL +import com.github.libretube.api.obj.Channel +import com.github.libretube.api.obj.ChannelTabResponse +import com.github.libretube.api.obj.CommentsPage +import com.github.libretube.api.obj.DeArrowContent +import com.github.libretube.api.obj.Playlist +import com.github.libretube.api.obj.SearchResult +import com.github.libretube.api.obj.SegmentData +import com.github.libretube.api.obj.StreamItem +import com.github.libretube.api.obj.Streams +import com.github.libretube.constants.PreferenceKeys +import com.github.libretube.helpers.PreferenceHelper + +class PipedMediaServiceRepository : MediaServiceRepository { + override suspend fun getTrending(region: String): List = + api.getTrending(region) + + override suspend fun getStreams(videoId: String): Streams = + api.getStreams(videoId) + + override suspend fun getComments(videoId: String): CommentsPage = + api.getComments(videoId) + + override suspend fun getSegments( + videoId: String, + category: String, + actionType: String? + ): SegmentData = api.getSegments(videoId, category, actionType) + + override suspend fun getDeArrowContent(videoIds: String): Map = + api.getDeArrowContent(videoIds) + + override suspend fun getCommentsNextPage(videoId: String, nextPage: String): CommentsPage = + api.getCommentsNextPage(videoId, nextPage) + + override suspend fun getSearchResults(searchQuery: String, filter: String): SearchResult = + api.getSearchResults(searchQuery, filter) + + override suspend fun getSearchResultsNextPage( + searchQuery: String, + filter: String, + nextPage: String + ): SearchResult = api.getSearchResultsNextPage(searchQuery, filter, nextPage) + + override suspend fun getSuggestions(query: String): List = + api.getSuggestions(query) + + override suspend fun getChannel(channelId: String): Channel = + api.getChannel(channelId) + + override suspend fun getChannelTab(data: String, nextPage: String?): ChannelTabResponse = + api.getChannelTab(data, nextPage) + + override suspend fun getChannelByName(channelName: String): Channel = + api.getChannelByName(channelName) + + override suspend fun getChannelNextPage(channelId: String, nextPage: String): Channel = + api.getChannelNextPage(channelId, nextPage) + + override suspend fun getPlaylist(playlistId: String): Playlist = + api.getPlaylist(playlistId) + + override suspend fun getPlaylistNextPage(playlistId: String, nextPage: String): Playlist = + api.getPlaylistNextPage(playlistId, nextPage) + + companion object { + val apiUrl get() = PreferenceHelper.getString(PreferenceKeys.FETCH_INSTANCE, PIPED_API_URL) + + private val api by resettableLazy(RetrofitInstance.apiLazyMgr) { + RetrofitInstance.buildRetrofitInstance(apiUrl) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/libretube/api/PlaylistsHelper.kt b/app/src/main/java/com/github/libretube/api/PlaylistsHelper.kt index 8b7442617..68abc5102 100644 --- a/app/src/main/java/com/github/libretube/api/PlaylistsHelper.kt +++ b/app/src/main/java/com/github/libretube/api/PlaylistsHelper.kt @@ -52,7 +52,7 @@ object PlaylistsHelper { suspend fun getPlaylist(playlistId: String): Playlist { // load locally stored playlists with the auth api return when (getPrivatePlaylistType(playlistId)) { - PlaylistType.PUBLIC -> RetrofitInstance.api.getPlaylist(playlistId) + PlaylistType.PUBLIC -> MediaServiceRepository.instance.getPlaylist(playlistId) else -> playlistsRepository.getPlaylist(playlistId) }.apply { relatedStreams = relatedStreams.deArrow() diff --git a/app/src/main/java/com/github/libretube/api/RetrofitInstance.kt b/app/src/main/java/com/github/libretube/api/RetrofitInstance.kt index 2fd527bcc..ec863143b 100644 --- a/app/src/main/java/com/github/libretube/api/RetrofitInstance.kt +++ b/app/src/main/java/com/github/libretube/api/RetrofitInstance.kt @@ -11,55 +11,33 @@ import retrofit2.converter.kotlinx.serialization.asConverterFactory import retrofit2.create object RetrofitInstance { - private const val PIPED_API_URL = "https://pipedapi.kavin.rocks" - val apiUrl get() = PreferenceHelper.getString(PreferenceKeys.FETCH_INSTANCE, PIPED_API_URL) + const val PIPED_API_URL = "https://pipedapi.kavin.rocks" + val authUrl - get() = when ( + get() = if ( PreferenceHelper.getBoolean( PreferenceKeys.AUTH_INSTANCE_TOGGLE, false ) ) { - true -> PreferenceHelper.getString( + PreferenceHelper.getString( PreferenceKeys.AUTH_INSTANCE, PIPED_API_URL ) - - false -> apiUrl + } else { + PipedMediaServiceRepository.apiUrl } - val lazyMgr = resettableManager() - private val kotlinxConverterFactory = JsonHelper.json + val apiLazyMgr = resettableManager() + val kotlinxConverterFactory = JsonHelper.json .asConverterFactory("application/json".toMediaType()) - private val httpClient by lazy { buildClient() } + val httpClient by lazy { buildClient() } - val api by resettableLazy(lazyMgr) { - Retrofit.Builder() - .baseUrl(apiUrl) - .client(httpClient) - .addConverterFactory(kotlinxConverterFactory) - .build() - .create() - } + val authApi = buildRetrofitInstance(authUrl) - val authApi by resettableLazy(lazyMgr) { - Retrofit.Builder() - .baseUrl(authUrl) - .client(httpClient) - .addConverterFactory(kotlinxConverterFactory) - .build() - .create() - } - - val externalApi by resettableLazy(lazyMgr) { - Retrofit.Builder() - .baseUrl(apiUrl) - .client(httpClient) - .addConverterFactory(kotlinxConverterFactory) - .build() - .create() - } + // the url provided here isn't actually used anywhere in the external api + val externalApi = buildRetrofitInstance(PIPED_API_URL) private fun buildClient(): OkHttpClient { val httpClient = OkHttpClient().newBuilder() @@ -74,4 +52,11 @@ object RetrofitInstance { return httpClient.build() } + + inline fun buildRetrofitInstance(apiUrl: String): T = Retrofit.Builder() + .baseUrl(apiUrl) + .client(httpClient) + .addConverterFactory(kotlinxConverterFactory) + .build() + .create() } diff --git a/app/src/main/java/com/github/libretube/api/StreamsExtractor.kt b/app/src/main/java/com/github/libretube/api/StreamsExtractor.kt index 955da92d9..1436decb3 100644 --- a/app/src/main/java/com/github/libretube/api/StreamsExtractor.kt +++ b/app/src/main/java/com/github/libretube/api/StreamsExtractor.kt @@ -85,7 +85,7 @@ fun StreamInfoItem.toStreamItem( object StreamsExtractor { suspend fun extractStreams(videoId: String): Streams = withContext(Dispatchers.IO) { if (!PlayerHelper.disablePipedProxy || !PlayerHelper.localStreamExtraction) { - return@withContext RetrofitInstance.api.getStreams(videoId).deArrow(videoId) + return@withContext MediaServiceRepository.instance.getStreams(videoId).deArrow(videoId) } val respAsync = async { diff --git a/app/src/main/java/com/github/libretube/helpers/ProxyHelper.kt b/app/src/main/java/com/github/libretube/helpers/ProxyHelper.kt index d3a2a03d7..d0af9d87a 100644 --- a/app/src/main/java/com/github/libretube/helpers/ProxyHelper.kt +++ b/app/src/main/java/com/github/libretube/helpers/ProxyHelper.kt @@ -1,5 +1,6 @@ package com.github.libretube.helpers +import com.github.libretube.api.PipedMediaServiceRepository import com.github.libretube.api.RetrofitInstance import com.github.libretube.constants.PreferenceKeys import kotlinx.coroutines.CoroutineScope @@ -11,9 +12,10 @@ object ProxyHelper { fun fetchProxyUrl() { CoroutineScope(Dispatchers.IO).launch { runCatching { - RetrofitInstance.api.getConfig().imageProxyUrl?.let { - PreferenceHelper.putString(PreferenceKeys.IMAGE_PROXY_URL, it) - } + RetrofitInstance.externalApi.getInstanceConfig(PipedMediaServiceRepository.apiUrl) + .imageProxyUrl?.let { + PreferenceHelper.putString(PreferenceKeys.IMAGE_PROXY_URL, it) + } } } } diff --git a/app/src/main/java/com/github/libretube/repo/LocalPlaylistsRepository.kt b/app/src/main/java/com/github/libretube/repo/LocalPlaylistsRepository.kt index 63c35c9c5..ba0d858e8 100644 --- a/app/src/main/java/com/github/libretube/repo/LocalPlaylistsRepository.kt +++ b/app/src/main/java/com/github/libretube/repo/LocalPlaylistsRepository.kt @@ -1,8 +1,8 @@ package com.github.libretube.repo +import com.github.libretube.api.MediaServiceRepository import com.github.libretube.api.PlaylistsHelper import com.github.libretube.api.PlaylistsHelper.MAX_CONCURRENT_IMPORT_CALLS -import com.github.libretube.api.RetrofitInstance import com.github.libretube.api.StreamsExtractor import com.github.libretube.api.obj.Playlist import com.github.libretube.api.obj.Playlists @@ -84,7 +84,7 @@ class LocalPlaylistsRepository: PlaylistRepository { } override suspend fun clonePlaylist(playlistId: String): String { - val playlist = RetrofitInstance.api.getPlaylist(playlistId) + val playlist = MediaServiceRepository.instance.getPlaylist(playlistId) val newPlaylist = createPlaylist(playlist.name ?: "Unknown name") PlaylistsHelper.addToPlaylist(newPlaylist, *playlist.relatedStreams.toTypedArray()) @@ -92,7 +92,7 @@ class LocalPlaylistsRepository: PlaylistRepository { var nextPage = playlist.nextpage while (nextPage != null) { nextPage = runCatching { - RetrofitInstance.api.getPlaylistNextPage(playlistId, nextPage!!).apply { + MediaServiceRepository.instance.getPlaylistNextPage(playlistId, nextPage!!).apply { PlaylistsHelper.addToPlaylist(newPlaylist, *relatedStreams.toTypedArray()) }.nextpage }.getOrNull() diff --git a/app/src/main/java/com/github/libretube/services/OnlinePlayerService.kt b/app/src/main/java/com/github/libretube/services/OnlinePlayerService.kt index e6dbe732e..56166588e 100644 --- a/app/src/main/java/com/github/libretube/services/OnlinePlayerService.kt +++ b/app/src/main/java/com/github/libretube/services/OnlinePlayerService.kt @@ -12,7 +12,7 @@ import androidx.media3.datasource.DefaultDataSource import androidx.media3.exoplayer.hls.HlsMediaSource import com.github.libretube.R import com.github.libretube.api.JsonHelper -import com.github.libretube.api.RetrofitInstance +import com.github.libretube.api.MediaServiceRepository import com.github.libretube.api.StreamsExtractor import com.github.libretube.api.SubscriptionHelper import com.github.libretube.api.obj.Segment @@ -20,7 +20,6 @@ import com.github.libretube.api.obj.Streams import com.github.libretube.constants.IntentData import com.github.libretube.constants.PreferenceKeys import com.github.libretube.db.DatabaseHelper -import com.github.libretube.db.DatabaseHolder import com.github.libretube.enums.PlayerCommand import com.github.libretube.extensions.parcelable import com.github.libretube.extensions.setMetadata @@ -206,7 +205,7 @@ open class OnlinePlayerService : AbstractPlayerService() { private fun fetchSponsorBlockSegments() = scope.launch(Dispatchers.IO) { runCatching { if (sponsorBlockConfig.isEmpty()) return@runCatching - sponsorBlockSegments = RetrofitInstance.api.getSegments( + sponsorBlockSegments = MediaServiceRepository.instance.getSegments( videoId, JsonHelper.json.encodeToString(sponsorBlockConfig.keys), """["skip","mute","full","poi","chapter"]""" diff --git a/app/src/main/java/com/github/libretube/services/PlaylistDownloadEnqueueService.kt b/app/src/main/java/com/github/libretube/services/PlaylistDownloadEnqueueService.kt index 1cef39970..56e85b25a 100644 --- a/app/src/main/java/com/github/libretube/services/PlaylistDownloadEnqueueService.kt +++ b/app/src/main/java/com/github/libretube/services/PlaylistDownloadEnqueueService.kt @@ -10,8 +10,8 @@ import androidx.lifecycle.LifecycleService import androidx.lifecycle.lifecycleScope import com.github.libretube.LibreTubeApp.Companion.PLAYLIST_DOWNLOAD_ENQUEUE_CHANNEL_NAME import com.github.libretube.R +import com.github.libretube.api.MediaServiceRepository import com.github.libretube.api.PlaylistsHelper -import com.github.libretube.api.RetrofitInstance import com.github.libretube.api.StreamsExtractor import com.github.libretube.api.obj.PipedStream import com.github.libretube.api.obj.StreamItem @@ -95,7 +95,7 @@ class PlaylistDownloadEnqueueService : LifecycleService() { private suspend fun enqueuePublicPlaylist() { val playlist = try { - RetrofitInstance.api.getPlaylist(playlistId) + MediaServiceRepository.instance.getPlaylist(playlistId) } catch (e: Exception) { toastFromMainDispatcher(e.localizedMessage.orEmpty()) stopSelf() @@ -111,7 +111,7 @@ class PlaylistDownloadEnqueueService : LifecycleService() { while (nextPage != null) { val playlistPage = runCatching { - RetrofitInstance.api.getPlaylistNextPage(playlistId, nextPage!!) + MediaServiceRepository.instance.getPlaylistNextPage(playlistId, nextPage!!) }.getOrNull() if (playlistPage == null && alreadyRetriedOnce) { diff --git a/app/src/main/java/com/github/libretube/ui/dialogs/SubmitDeArrowDialog.kt b/app/src/main/java/com/github/libretube/ui/dialogs/SubmitDeArrowDialog.kt index 933b56fe9..82d49c674 100644 --- a/app/src/main/java/com/github/libretube/ui/dialogs/SubmitDeArrowDialog.kt +++ b/app/src/main/java/com/github/libretube/ui/dialogs/SubmitDeArrowDialog.kt @@ -6,6 +6,7 @@ import android.os.Bundle import androidx.fragment.app.DialogFragment import androidx.lifecycle.lifecycleScope import com.github.libretube.R +import com.github.libretube.api.MediaServiceRepository import com.github.libretube.api.RetrofitInstance import com.github.libretube.api.obj.DeArrowBody import com.github.libretube.api.obj.DeArrowSubmitThumbnail @@ -71,7 +72,7 @@ class SubmitDeArrowDialog: DialogFragment() { private suspend fun fetchDeArrowData() { val data = try { withContext(Dispatchers.IO) { - RetrofitInstance.api.getDeArrowContent(videoId) + MediaServiceRepository.instance.getDeArrowContent(videoId) }.getOrElse(videoId) { return } } catch (e: Exception) { return diff --git a/app/src/main/java/com/github/libretube/ui/dialogs/SubmitSegmentDialog.kt b/app/src/main/java/com/github/libretube/ui/dialogs/SubmitSegmentDialog.kt index 135b138a4..ce5fb7061 100644 --- a/app/src/main/java/com/github/libretube/ui/dialogs/SubmitSegmentDialog.kt +++ b/app/src/main/java/com/github/libretube/ui/dialogs/SubmitSegmentDialog.kt @@ -10,6 +10,7 @@ import androidx.fragment.app.DialogFragment import androidx.lifecycle.lifecycleScope import com.github.libretube.R import com.github.libretube.api.JsonHelper +import com.github.libretube.api.MediaServiceRepository import com.github.libretube.api.RetrofitInstance import com.github.libretube.api.obj.Segment import com.github.libretube.constants.IntentData @@ -146,7 +147,7 @@ class SubmitSegmentDialog : DialogFragment() { private suspend fun fetchSegments() { val categories = resources.getStringArray(R.array.sponsorBlockSegments).toList() segments = try { - RetrofitInstance.api.getSegments(videoId, JsonHelper.json.encodeToString(categories)).segments + MediaServiceRepository.instance.getSegments(videoId, JsonHelper.json.encodeToString(categories)).segments } catch (e: Exception) { Log.e(TAG(), e.toString()) return diff --git a/app/src/main/java/com/github/libretube/ui/fragments/ChannelContentFragment.kt b/app/src/main/java/com/github/libretube/ui/fragments/ChannelContentFragment.kt index ff753aa37..ba546d0bf 100644 --- a/app/src/main/java/com/github/libretube/ui/fragments/ChannelContentFragment.kt +++ b/app/src/main/java/com/github/libretube/ui/fragments/ChannelContentFragment.kt @@ -11,7 +11,7 @@ import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.github.libretube.R -import com.github.libretube.api.RetrofitInstance +import com.github.libretube.api.MediaServiceRepository import com.github.libretube.api.obj.ChannelTab import com.github.libretube.api.obj.StreamItem import com.github.libretube.constants.IntentData @@ -47,7 +47,7 @@ class ChannelContentFragment : DynamicLayoutManagerFragment(R.layout.fragment_ch private suspend fun fetchChannelNextPage(nextPage: String): String? { val response = withContext(Dispatchers.IO) { - RetrofitInstance.api.getChannelNextPage(channelId!!, nextPage).apply { + MediaServiceRepository.instance.getChannelNextPage(channelId!!, nextPage).apply { relatedStreams = relatedStreams.deArrow() } } @@ -57,7 +57,7 @@ class ChannelContentFragment : DynamicLayoutManagerFragment(R.layout.fragment_ch private suspend fun fetchTabNextPage(nextPage: String, tab: ChannelTab): String? { val newContent = withContext(Dispatchers.IO) { - RetrofitInstance.api.getChannelTab(tab.data, nextPage) + MediaServiceRepository.instance.getChannelTab(tab.data, nextPage) }.apply { content = content.deArrow() } @@ -77,7 +77,7 @@ class ChannelContentFragment : DynamicLayoutManagerFragment(R.layout.fragment_ch private fun loadChannelTab(tab: ChannelTab) = lifecycleScope.launch { val response = try { withContext(Dispatchers.IO) { - RetrofitInstance.api.getChannelTab(tab.data) + MediaServiceRepository.instance.getChannelTab(tab.data) }.apply { content = content.deArrow() } diff --git a/app/src/main/java/com/github/libretube/ui/fragments/ChannelFragment.kt b/app/src/main/java/com/github/libretube/ui/fragments/ChannelFragment.kt index d9b0d8b2d..483aec078 100644 --- a/app/src/main/java/com/github/libretube/ui/fragments/ChannelFragment.kt +++ b/app/src/main/java/com/github/libretube/ui/fragments/ChannelFragment.kt @@ -13,7 +13,7 @@ import androidx.recyclerview.widget.RecyclerView import androidx.viewpager2.adapter.FragmentStateAdapter import androidx.viewpager2.widget.ViewPager2 import com.github.libretube.R -import com.github.libretube.api.RetrofitInstance +import com.github.libretube.api.MediaServiceRepository import com.github.libretube.api.obj.ChannelTab import com.github.libretube.api.obj.StreamItem import com.github.libretube.constants.IntentData @@ -122,9 +122,9 @@ class ChannelFragment : DynamicLayoutManagerFragment(R.layout.fragment_channel) val response = try { withContext(Dispatchers.IO) { if (channelId != null) { - RetrofitInstance.api.getChannel(channelId!!) + MediaServiceRepository.instance.getChannel(channelId!!) } else { - RetrofitInstance.api.getChannelByName(channelName!!) + MediaServiceRepository.instance.getChannelByName(channelName!!) }.apply { relatedStreams = relatedStreams.deArrow() } diff --git a/app/src/main/java/com/github/libretube/ui/fragments/PlaylistFragment.kt b/app/src/main/java/com/github/libretube/ui/fragments/PlaylistFragment.kt index de8324bbb..1217173ef 100644 --- a/app/src/main/java/com/github/libretube/ui/fragments/PlaylistFragment.kt +++ b/app/src/main/java/com/github/libretube/ui/fragments/PlaylistFragment.kt @@ -18,8 +18,8 @@ import androidx.navigation.fragment.navArgs import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView import com.github.libretube.R +import com.github.libretube.api.MediaServiceRepository import com.github.libretube.api.PlaylistsHelper -import com.github.libretube.api.RetrofitInstance import com.github.libretube.api.obj.Playlist import com.github.libretube.api.obj.StreamItem import com.github.libretube.constants.IntentData @@ -382,7 +382,7 @@ class PlaylistFragment : DynamicLayoutManagerFragment(R.layout.fragment_playlist val response = try { withContext(Dispatchers.IO) { // load locally stored playlists with the auth api - RetrofitInstance.api.getPlaylistNextPage(playlistId, nextPage!!) + MediaServiceRepository.instance.getPlaylistNextPage(playlistId, nextPage!!) } } catch (e: Exception) { context?.toastFromMainDispatcher(e.localizedMessage.orEmpty()) diff --git a/app/src/main/java/com/github/libretube/ui/fragments/SearchSuggestionsFragment.kt b/app/src/main/java/com/github/libretube/ui/fragments/SearchSuggestionsFragment.kt index 0bcf0cce1..9f5d898f5 100644 --- a/app/src/main/java/com/github/libretube/ui/fragments/SearchSuggestionsFragment.kt +++ b/app/src/main/java/com/github/libretube/ui/fragments/SearchSuggestionsFragment.kt @@ -12,7 +12,7 @@ import androidx.lifecycle.lifecycleScope import androidx.lifecycle.map import androidx.navigation.fragment.findNavController import com.github.libretube.R -import com.github.libretube.api.RetrofitInstance +import com.github.libretube.api.MediaServiceRepository import com.github.libretube.constants.IntentData import com.github.libretube.constants.PreferenceKeys import com.github.libretube.databinding.FragmentSearchSuggestionsBinding @@ -102,7 +102,7 @@ class SearchSuggestionsFragment : Fragment(R.layout.fragment_search_suggestions) lifecycleScope.launch { val response = try { withContext(Dispatchers.IO) { - RetrofitInstance.api.getSuggestions(query) + MediaServiceRepository.instance.getSuggestions(query) } } catch (e: Exception) { Log.e(TAG(), e.toString()) diff --git a/app/src/main/java/com/github/libretube/ui/models/HomeViewModel.kt b/app/src/main/java/com/github/libretube/ui/models/HomeViewModel.kt index 6f862aaba..f3f21f02f 100644 --- a/app/src/main/java/com/github/libretube/ui/models/HomeViewModel.kt +++ b/app/src/main/java/com/github/libretube/ui/models/HomeViewModel.kt @@ -4,8 +4,8 @@ import android.content.Context import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.github.libretube.api.MediaServiceRepository import com.github.libretube.api.PlaylistsHelper -import com.github.libretube.api.RetrofitInstance import com.github.libretube.api.SubscriptionHelper import com.github.libretube.api.obj.Playlists import com.github.libretube.api.obj.StreamItem @@ -78,7 +78,7 @@ class HomeViewModel : ViewModel() { runSafely( onSuccess = { videos -> trending.updateIfChanged(videos) }, - ioBlock = { RetrofitInstance.api.getTrending(region).deArrow().take(10) } + ioBlock = { MediaServiceRepository.instance.getTrending(region).deArrow().take(10) } ) } diff --git a/app/src/main/java/com/github/libretube/ui/models/TrendsViewModel.kt b/app/src/main/java/com/github/libretube/ui/models/TrendsViewModel.kt index 4cc928e3a..f2ecfb042 100644 --- a/app/src/main/java/com/github/libretube/ui/models/TrendsViewModel.kt +++ b/app/src/main/java/com/github/libretube/ui/models/TrendsViewModel.kt @@ -8,7 +8,7 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.github.libretube.R -import com.github.libretube.api.RetrofitInstance +import com.github.libretube.api.MediaServiceRepository import com.github.libretube.api.obj.StreamItem import com.github.libretube.extensions.TAG import com.github.libretube.helpers.LocaleHelper @@ -28,7 +28,7 @@ class TrendsViewModel : ViewModel() { try { val region = LocaleHelper.getTrendingRegion(context) val response = withContext(Dispatchers.IO) { - RetrofitInstance.api.getTrending(region).deArrow() + MediaServiceRepository.instance.getTrending(region).deArrow() } trendingVideos.postValue(response) } catch (e: IOException) { diff --git a/app/src/main/java/com/github/libretube/ui/models/WelcomeViewModel.kt b/app/src/main/java/com/github/libretube/ui/models/WelcomeViewModel.kt index 7c31e725c..e21e40b60 100644 --- a/app/src/main/java/com/github/libretube/ui/models/WelcomeViewModel.kt +++ b/app/src/main/java/com/github/libretube/ui/models/WelcomeViewModel.kt @@ -77,7 +77,7 @@ class WelcomeViewModel( private fun refreshAndNavigate() { // refresh the api urls since they have changed likely - RetrofitInstance.lazyMgr.reset() + RetrofitInstance.apiLazyMgr.reset() savedStateHandle[UI_STATE] = _uiState.value.copy(navigateToMain = Unit) } diff --git a/app/src/main/java/com/github/libretube/ui/models/sources/CommentPagingSource.kt b/app/src/main/java/com/github/libretube/ui/models/sources/CommentPagingSource.kt index efbf339c2..abba56dfc 100644 --- a/app/src/main/java/com/github/libretube/ui/models/sources/CommentPagingSource.kt +++ b/app/src/main/java/com/github/libretube/ui/models/sources/CommentPagingSource.kt @@ -2,7 +2,7 @@ package com.github.libretube.ui.models.sources import androidx.paging.PagingSource import androidx.paging.PagingState -import com.github.libretube.api.RetrofitInstance +import com.github.libretube.api.MediaServiceRepository import com.github.libretube.api.obj.Comment class CommentPagingSource( @@ -14,8 +14,8 @@ class CommentPagingSource( override suspend fun load(params: LoadParams): LoadResult { return try { val result = params.key?.let { - RetrofitInstance.api.getCommentsNextPage(videoId, it) - } ?: RetrofitInstance.api.getComments(videoId) + MediaServiceRepository.instance.getCommentsNextPage(videoId, it) + } ?: MediaServiceRepository.instance.getComments(videoId) if (result.commentCount > 0) onCommentCount(result.commentCount) diff --git a/app/src/main/java/com/github/libretube/ui/models/sources/CommentRepliesPagingSource.kt b/app/src/main/java/com/github/libretube/ui/models/sources/CommentRepliesPagingSource.kt index 7b0b98d39..b7725ba46 100644 --- a/app/src/main/java/com/github/libretube/ui/models/sources/CommentRepliesPagingSource.kt +++ b/app/src/main/java/com/github/libretube/ui/models/sources/CommentRepliesPagingSource.kt @@ -2,7 +2,7 @@ package com.github.libretube.ui.models.sources import androidx.paging.PagingSource import androidx.paging.PagingState -import com.github.libretube.api.RetrofitInstance +import com.github.libretube.api.MediaServiceRepository import com.github.libretube.api.obj.Comment class CommentRepliesPagingSource( @@ -14,7 +14,7 @@ class CommentRepliesPagingSource( override suspend fun load(params: LoadParams): LoadResult { return try { val key = params.key.orEmpty().ifEmpty { originalComment.repliesPage.orEmpty() } - val result = RetrofitInstance.api.getCommentsNextPage(videoId, key) + val result = MediaServiceRepository.instance.getCommentsNextPage(videoId, key) val replies = result.comments.toMutableList() if (params.key.isNullOrEmpty()) { diff --git a/app/src/main/java/com/github/libretube/ui/models/sources/SearchPagingSource.kt b/app/src/main/java/com/github/libretube/ui/models/sources/SearchPagingSource.kt index e6cf7f260..d2c0d283a 100644 --- a/app/src/main/java/com/github/libretube/ui/models/sources/SearchPagingSource.kt +++ b/app/src/main/java/com/github/libretube/ui/models/sources/SearchPagingSource.kt @@ -2,7 +2,7 @@ package com.github.libretube.ui.models.sources import androidx.paging.PagingSource import androidx.paging.PagingState -import com.github.libretube.api.RetrofitInstance +import com.github.libretube.api.MediaServiceRepository import com.github.libretube.api.obj.ContentItem import com.github.libretube.util.deArrow @@ -15,8 +15,8 @@ class SearchPagingSource( override suspend fun load(params: LoadParams): LoadResult { return try { val result = params.key?.let { - RetrofitInstance.api.getSearchResultsNextPage(searchQuery, searchFilter, it) - } ?: RetrofitInstance.api.getSearchResults(searchQuery, searchFilter) + MediaServiceRepository.instance.getSearchResultsNextPage(searchQuery, searchFilter, it) + } ?: MediaServiceRepository.instance.getSearchResults(searchQuery, searchFilter) LoadResult.Page(result.items.deArrow(), null, result.nextpage) } catch (e: Exception) { LoadResult.Error(e) diff --git a/app/src/main/java/com/github/libretube/ui/preferences/InstanceSettings.kt b/app/src/main/java/com/github/libretube/ui/preferences/InstanceSettings.kt index 1d415e201..903311efd 100644 --- a/app/src/main/java/com/github/libretube/ui/preferences/InstanceSettings.kt +++ b/app/src/main/java/com/github/libretube/ui/preferences/InstanceSettings.kt @@ -11,6 +11,7 @@ import androidx.preference.SwitchPreferenceCompat import androidx.recyclerview.widget.LinearLayoutManager import com.github.libretube.R import com.github.libretube.api.InstanceRepository +import com.github.libretube.api.PipedMediaServiceRepository import com.github.libretube.api.RetrofitInstance import com.github.libretube.api.obj.PipedInstance import com.github.libretube.constants.IntentData @@ -68,13 +69,13 @@ class InstanceSettings : BasePreferenceFragment() { } authInstance.setOnPreferenceChangeListener { _, _ -> - RetrofitInstance.lazyMgr.reset() + RetrofitInstance.apiLazyMgr.reset() logoutAndUpdateUI() true } authInstanceToggle.setOnPreferenceChangeListener { _, _ -> - RetrofitInstance.lazyMgr.reset() + RetrofitInstance.apiLazyMgr.reset() logoutAndUpdateUI() true } @@ -147,7 +148,7 @@ class InstanceSettings : BasePreferenceFragment() { // add the currently used instances to the list if they're currently down / not part // of the public instances list - for (apiUrl in listOf(RetrofitInstance.apiUrl, RetrofitInstance.authUrl)) { + for (apiUrl in listOf(PipedMediaServiceRepository.apiUrl, RetrofitInstance.authUrl)) { if (instances.none { it.apiUrl == apiUrl }) { val origin = apiUrl.toHttpUrl().host instances.add(PipedInstance(origin, apiUrl, isCurrentlyDown = true)) @@ -215,7 +216,7 @@ class InstanceSettings : BasePreferenceFragment() { if (!authInstanceToggle.isChecked) { logoutAndUpdateUI() } - RetrofitInstance.lazyMgr.reset() + RetrofitInstance.apiLazyMgr.reset() ActivityCompat.recreate(requireActivity()) } diff --git a/app/src/main/java/com/github/libretube/ui/sheets/ChannelOptionsBottomSheet.kt b/app/src/main/java/com/github/libretube/ui/sheets/ChannelOptionsBottomSheet.kt index 0de465668..49840b72a 100644 --- a/app/src/main/java/com/github/libretube/ui/sheets/ChannelOptionsBottomSheet.kt +++ b/app/src/main/java/com/github/libretube/ui/sheets/ChannelOptionsBottomSheet.kt @@ -4,7 +4,7 @@ import android.os.Bundle import android.util.Log import androidx.core.os.bundleOf import com.github.libretube.R -import com.github.libretube.api.RetrofitInstance +import com.github.libretube.api.MediaServiceRepository import com.github.libretube.constants.IntentData import com.github.libretube.enums.ShareObjectType import com.github.libretube.extensions.TAG @@ -66,7 +66,7 @@ class ChannelOptionsBottomSheet : BaseBottomSheet() { R.string.play_latest_videos -> { try { val channel = withContext(Dispatchers.IO) { - RetrofitInstance.api.getChannel(channelId) + MediaServiceRepository.instance.getChannel(channelId) } channel.relatedStreams.firstOrNull()?.url?.toID()?.let { NavigationHelper.navigateVideo( @@ -83,7 +83,7 @@ class ChannelOptionsBottomSheet : BaseBottomSheet() { R.string.playOnBackground -> { try { val channel = withContext(Dispatchers.IO) { - RetrofitInstance.api.getChannel(channelId) + MediaServiceRepository.instance.getChannel(channelId) } channel.relatedStreams.firstOrNull()?.url?.toID()?.let { BackgroundHelper.playOnBackground( diff --git a/app/src/main/java/com/github/libretube/ui/sheets/PlaylistOptionsBottomSheet.kt b/app/src/main/java/com/github/libretube/ui/sheets/PlaylistOptionsBottomSheet.kt index 76f5b04f5..3d31604b4 100644 --- a/app/src/main/java/com/github/libretube/ui/sheets/PlaylistOptionsBottomSheet.kt +++ b/app/src/main/java/com/github/libretube/ui/sheets/PlaylistOptionsBottomSheet.kt @@ -3,8 +3,8 @@ package com.github.libretube.ui.sheets import android.os.Bundle import androidx.core.os.bundleOf import com.github.libretube.R +import com.github.libretube.api.MediaServiceRepository import com.github.libretube.api.PlaylistsHelper -import com.github.libretube.api.RetrofitInstance import com.github.libretube.constants.IntentData import com.github.libretube.db.DatabaseHolder import com.github.libretube.enums.ImportFormat @@ -176,7 +176,7 @@ class PlaylistOptionsBottomSheet : BaseBottomSheet() { DatabaseHolder.Database.playlistBookmarkDao().deleteById(playlistId) } else { val bookmark = try { - RetrofitInstance.api.getPlaylist(playlistId) + MediaServiceRepository.instance.getPlaylist(playlistId) } catch (e: Exception) { return@withContext }.toPlaylistBookmark(playlistId) diff --git a/app/src/main/java/com/github/libretube/util/DeArrowUtil.kt b/app/src/main/java/com/github/libretube/util/DeArrowUtil.kt index 3677d38e6..866990d37 100644 --- a/app/src/main/java/com/github/libretube/util/DeArrowUtil.kt +++ b/app/src/main/java/com/github/libretube/util/DeArrowUtil.kt @@ -1,7 +1,7 @@ package com.github.libretube.util import android.util.Log -import com.github.libretube.api.RetrofitInstance +import com.github.libretube.api.MediaServiceRepository import com.github.libretube.api.obj.ContentItem import com.github.libretube.api.obj.DeArrowContent import com.github.libretube.api.obj.StreamItem @@ -26,7 +26,7 @@ object DeArrowUtil { val videoIdsString = videoIds.mapTo(TreeSet()) { it }.joinToString(",") return try { - RetrofitInstance.api.getDeArrowContent(videoIdsString) + MediaServiceRepository.instance.getDeArrowContent(videoIdsString) } catch (e: Exception) { Log.e(this::class.java.name, "Failed to fetch DeArrow content: ${e.message}") null diff --git a/app/src/main/java/com/github/libretube/util/PlayingQueue.kt b/app/src/main/java/com/github/libretube/util/PlayingQueue.kt index d82c5f60f..bd2a3b97a 100644 --- a/app/src/main/java/com/github/libretube/util/PlayingQueue.kt +++ b/app/src/main/java/com/github/libretube/util/PlayingQueue.kt @@ -1,6 +1,7 @@ package com.github.libretube.util import androidx.media3.common.Player +import com.github.libretube.api.MediaServiceRepository import com.github.libretube.api.PlaylistsHelper import com.github.libretube.api.RetrofitInstance import com.github.libretube.api.StreamsExtractor @@ -158,7 +159,7 @@ object PlayingQueue { ) { var playlistNextPage = nextPage while (playlistNextPage != null) { - RetrofitInstance.api.getPlaylistNextPage(playlistId, playlistNextPage).run { + MediaServiceRepository.instance.getPlaylistNextPage(playlistId, playlistNextPage).run { addToQueueAsync(relatedStreams, isMainList = isMainList) playlistNextPage = this.nextpage } @@ -177,7 +178,7 @@ object PlayingQueue { var channelNextPage = nextPage var pageIndex = 1 while (channelNextPage != null && pageIndex < 10) { - RetrofitInstance.api.getChannelNextPage(channelId, channelNextPage).run { + MediaServiceRepository.instance.getChannelNextPage(channelId, channelNextPage).run { addToQueueAsync(relatedStreams) channelNextPage = this.nextpage pageIndex++ @@ -186,7 +187,7 @@ object PlayingQueue { } private fun insertChannel(channelId: String, newCurrentStream: StreamItem) = runCatchingIO { - val channel = RetrofitInstance.api.getChannel(channelId) + val channel = MediaServiceRepository.instance.getChannel(channelId) addToQueueAsync(channel.relatedStreams, newCurrentStream) if (channel.nextpage == null) return@runCatchingIO fetchMoreFromChannel(channelId, channel.nextpage) From 7db8212c721eba91a9a3a71a4175e6bceb0cf04f Mon Sep 17 00:00:00 2001 From: Bnyro Date: Sat, 1 Mar 2025 20:03:51 +0100 Subject: [PATCH 2/3] feat: add support for using local NewPipe Extractor --- ...msExtractionPipedMediaServiceRepository.kt | 7 + .../libretube/api/MediaServiceRepository.kt | 22 +- .../api/NewPipeMediaServiceRepository.kt | 467 ++++++++++++++++++ .../api/PipedMediaServiceRepository.kt | 17 +- .../github/libretube/api/StreamsExtractor.kt | 176 ------- .../com/github/libretube/api/obj/Channel.kt | 2 +- .../github/libretube/api/obj/SearchResult.kt | 2 - .../repo/LocalPlaylistsRepository.kt | 3 +- .../libretube/services/DownloadService.kt | 13 +- .../libretube/services/OnlinePlayerService.kt | 11 +- .../PlaylistDownloadEnqueueService.kt | 3 +- .../ui/activities/AddToPlaylistActivity.kt | 4 +- .../ui/adapters/SearchChannelAdapter.kt | 6 +- .../libretube/ui/dialogs/DownloadDialog.kt | 15 +- .../ui/models/sources/CommentPagingSource.kt | 10 +- .../sources/CommentRepliesPagingSource.kt | 6 +- .../ui/models/sources/SearchPagingSource.kt | 13 +- .../com/github/libretube/util/DeArrowUtil.kt | 3 +- .../com/github/libretube/util/PlayingQueue.kt | 4 +- 19 files changed, 559 insertions(+), 225 deletions(-) create mode 100644 app/src/main/java/com/github/libretube/api/LocalStreamsExtractionPipedMediaServiceRepository.kt create mode 100644 app/src/main/java/com/github/libretube/api/NewPipeMediaServiceRepository.kt delete mode 100644 app/src/main/java/com/github/libretube/api/StreamsExtractor.kt diff --git a/app/src/main/java/com/github/libretube/api/LocalStreamsExtractionPipedMediaServiceRepository.kt b/app/src/main/java/com/github/libretube/api/LocalStreamsExtractionPipedMediaServiceRepository.kt new file mode 100644 index 000000000..3803818e7 --- /dev/null +++ b/app/src/main/java/com/github/libretube/api/LocalStreamsExtractionPipedMediaServiceRepository.kt @@ -0,0 +1,7 @@ +package com.github.libretube.api + +class LocalStreamsExtractionPipedMediaServiceRepository: PipedMediaServiceRepository() { + private val newPipeDelegate = NewPipeMediaServiceRepository() + + override suspend fun getStreams(videoId: String) = newPipeDelegate.getStreams(videoId) +} \ No newline at end of file diff --git a/app/src/main/java/com/github/libretube/api/MediaServiceRepository.kt b/app/src/main/java/com/github/libretube/api/MediaServiceRepository.kt index bbbbdc753..9212a5136 100644 --- a/app/src/main/java/com/github/libretube/api/MediaServiceRepository.kt +++ b/app/src/main/java/com/github/libretube/api/MediaServiceRepository.kt @@ -9,16 +9,27 @@ import com.github.libretube.api.obj.SearchResult import com.github.libretube.api.obj.SegmentData import com.github.libretube.api.obj.StreamItem import com.github.libretube.api.obj.Streams +import com.github.libretube.helpers.PlayerHelper interface MediaServiceRepository { suspend fun getTrending(region: String): List suspend fun getStreams(videoId: String): Streams suspend fun getComments(videoId: String): CommentsPage - suspend fun getSegments(videoId: String, category: String, actionType: String? = null): SegmentData + suspend fun getSegments( + videoId: String, + category: String, + actionType: String? = null + ): SegmentData + suspend fun getDeArrowContent(videoIds: String): Map suspend fun getCommentsNextPage(videoId: String, nextPage: String): CommentsPage suspend fun getSearchResults(searchQuery: String, filter: String): SearchResult - suspend fun getSearchResultsNextPage(searchQuery: String, filter: String, nextPage: String): SearchResult + suspend fun getSearchResultsNextPage( + searchQuery: String, + filter: String, + nextPage: String + ): SearchResult + suspend fun getSuggestions(query: String): List suspend fun getChannel(channelId: String): Channel suspend fun getChannelTab(data: String, nextPage: String? = null): ChannelTabResponse @@ -29,7 +40,12 @@ interface MediaServiceRepository { companion object { val instance by lazy { - PipedMediaServiceRepository() + if (PlayerHelper.disablePipedProxy && PlayerHelper.localStreamExtraction) { + // TODO: LocalStreamsExtractionPipedMediaServiceRepository() + NewPipeMediaServiceRepository() + } else { + PipedMediaServiceRepository() + } } } } \ No newline at end of file diff --git a/app/src/main/java/com/github/libretube/api/NewPipeMediaServiceRepository.kt b/app/src/main/java/com/github/libretube/api/NewPipeMediaServiceRepository.kt new file mode 100644 index 000000000..933403f6b --- /dev/null +++ b/app/src/main/java/com/github/libretube/api/NewPipeMediaServiceRepository.kt @@ -0,0 +1,467 @@ +package com.github.libretube.api + +import com.github.libretube.api.obj.Channel +import com.github.libretube.api.obj.ChannelTab +import com.github.libretube.api.obj.ChannelTabResponse +import com.github.libretube.api.obj.ChapterSegment +import com.github.libretube.api.obj.Comment +import com.github.libretube.api.obj.CommentsPage +import com.github.libretube.api.obj.ContentItem +import com.github.libretube.api.obj.DeArrowContent +import com.github.libretube.api.obj.MetaInfo +import com.github.libretube.api.obj.PipedStream +import com.github.libretube.api.obj.Playlist +import com.github.libretube.api.obj.PreviewFrames +import com.github.libretube.api.obj.SearchResult +import com.github.libretube.api.obj.SegmentData +import com.github.libretube.api.obj.StreamItem +import com.github.libretube.api.obj.StreamItem.Companion.TYPE_CHANNEL +import com.github.libretube.api.obj.StreamItem.Companion.TYPE_PLAYLIST +import com.github.libretube.api.obj.StreamItem.Companion.TYPE_STREAM +import com.github.libretube.api.obj.Streams +import com.github.libretube.api.obj.Subtitle +import com.github.libretube.extensions.toID +import com.github.libretube.helpers.NewPipeExtractorInstance +import com.github.libretube.helpers.PlayerHelper +import com.github.libretube.ui.dialogs.ShareDialog.Companion.YOUTUBE_FRONTEND_URL +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.withContext +import kotlinx.datetime.toKotlinInstant +import kotlinx.serialization.Serializable +import kotlinx.serialization.encodeToString +import org.schabi.newpipe.extractor.InfoItem +import org.schabi.newpipe.extractor.Page +import org.schabi.newpipe.extractor.channel.ChannelInfo +import org.schabi.newpipe.extractor.channel.ChannelInfoItem +import org.schabi.newpipe.extractor.channel.tabs.ChannelTabInfo +import org.schabi.newpipe.extractor.channel.tabs.ChannelTabs +import org.schabi.newpipe.extractor.comments.CommentsInfo +import org.schabi.newpipe.extractor.comments.CommentsInfoItem +import org.schabi.newpipe.extractor.kiosk.KioskInfo +import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler +import org.schabi.newpipe.extractor.localization.ContentCountry +import org.schabi.newpipe.extractor.playlist.PlaylistInfo +import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem +import org.schabi.newpipe.extractor.search.SearchInfo +import org.schabi.newpipe.extractor.stream.AudioStream +import org.schabi.newpipe.extractor.stream.StreamInfo +import org.schabi.newpipe.extractor.stream.StreamInfoItem +import org.schabi.newpipe.extractor.stream.VideoStream + + +private fun VideoStream.toPipedStream() = PipedStream( + url = content, + codec = codec, + format = format?.toString(), + height = height, + width = width, + quality = getResolution(), + mimeType = format?.mimeType, + bitrate = bitrate, + initStart = initStart, + initEnd = initEnd, + indexStart = indexStart, + indexEnd = indexEnd, + fps = fps, + contentLength = itagItem?.contentLength ?: 0L +) + +private fun AudioStream.toPipedStream() = PipedStream( + url = content, + format = format?.toString(), + quality = "$averageBitrate bits", + bitrate = bitrate, + mimeType = format?.mimeType, + initStart = initStart, + initEnd = initEnd, + indexStart = indexStart, + indexEnd = indexEnd, + contentLength = itagItem?.contentLength ?: 0L, + codec = codec, + audioTrackId = audioTrackId, + audioTrackName = audioTrackName, + audioTrackLocale = audioLocale?.toLanguageTag(), + audioTrackType = audioTrackType?.name, + videoOnly = false +) + +fun StreamInfoItem.toStreamItem( + uploaderAvatarUrl: String? = null +) = StreamItem( + type = TYPE_STREAM, + url = url.toID(), + title = name, + uploaded = uploadDate?.offsetDateTime()?.toEpochSecond()?.times(1000) ?: -1, + uploadedDate = textualUploadDate ?: uploadDate?.offsetDateTime()?.toLocalDateTime() + ?.toLocalDate() + ?.toString(), + uploaderName = uploaderName, + uploaderUrl = uploaderUrl.toID(), + uploaderAvatar = uploaderAvatarUrl ?: uploaderAvatars.maxByOrNull { it.height }?.url, + thumbnail = thumbnails.maxByOrNull { it.height }?.url, + duration = duration, + views = viewCount, + uploaderVerified = isUploaderVerified, + shortDescription = shortDescription, + isShort = isShortFormContent +) + +fun InfoItem.toContentItem() = when (this) { + is StreamInfoItem -> ContentItem( + url = url.toID(), + type = TYPE_STREAM, + thumbnail = thumbnails.maxByOrNull { it.height }?.url.orEmpty(), + title = name, + uploaderAvatar = uploaderAvatars.maxByOrNull { it.height }?.url.orEmpty(), + uploaderUrl = uploaderUrl.toID(), + uploaderName = uploaderName, + uploaded = uploadDate?.offsetDateTime()?.toInstant()?.toEpochMilli() ?: -1, + isShort = isShortFormContent, + views = viewCount, + shortDescription = shortDescription, + verified = isUploaderVerified, + duration = duration + ) + + is ChannelInfoItem -> ContentItem( + url = url.toID(), + name = name, + type = TYPE_CHANNEL, + thumbnail = thumbnails.maxByOrNull { it.height }?.url.orEmpty(), + subscribers = subscriberCount, + videos = streamCount + ) + + is PlaylistInfoItem -> ContentItem( + url = url.toID(), + type = TYPE_PLAYLIST, + title = name, + shortDescription = description.content, + thumbnail = thumbnails.maxByOrNull { it.height }?.url.orEmpty(), + videos = streamCount, + uploaderVerified = isUploaderVerified, + uploaderName = uploaderName, + uploaderUrl = uploaderUrl?.toID() + ) + + else -> null +} + +fun ChannelInfo.toChannel() = Channel( + id = id, + name = name, + description = description, + verified = isVerified, + avatarUrl = avatars.maxByOrNull { it.height }?.url, + bannerUrl = banners.maxByOrNull { it.height }?.url, + tabs = tabs.filterNot { it.contentFilters.contains(ChannelTabs.VIDEOS) } + .map { ChannelTab(it.contentFilters.first().lowercase(), it.toTabDataString()) }, + subscriberCount = subscriberCount +) + +fun PlaylistInfo.toPlaylist() = Playlist( + name = name, + description = description?.content, + thumbnailUrl = thumbnails.maxByOrNull { it.height }?.url, + uploaderUrl = uploaderUrl.toID(), + bannerUrl = banners.maxByOrNull { it.height }?.url, + uploader = uploaderName, + uploaderAvatar = uploaderAvatars.maxByOrNull { it.height }?.url, + videos = streamCount.toInt(), + relatedStreams = relatedItems.map { it.toStreamItem() }, + nextpage = nextPage?.toNextPageString() +) + +fun CommentsInfoItem.toComment() = Comment( + author = uploaderName, + commentId = commentId, + commentText = commentText.content, + commentedTime = textualUploadDate, + commentorUrl = uploaderUrl.toID(), + hearted = isHeartedByUploader, + creatorReplied = hasCreatorReply(), + likeCount = likeCount.toLong(), + pinned = isPinned, + verified = isUploaderVerified, + replyCount = replyCount.toLong(), + repliesPage = replies?.toNextPageString(), + thumbnail = thumbnails.maxByOrNull { it.height }?.url.orEmpty() +) + +// the following classes are necessary because kotlinx can't deserialize +// classes from external libraries as they're not annotated +@Serializable +private data class NextPage( + val url: String? = null, + val id: String? = null, + val ids: List? = null, + val cookies: Map? = null, + val body: String? = null +) + +fun Page.toNextPageString() = JsonHelper.json.encodeToString( + NextPage(url, id, ids, cookies, body?.toString()) +) + +fun String.toPage(): Page = with(JsonHelper.json.decodeFromString(this)) { + return Page(url, id, ids, cookies, body?.toByteArray()) +} + +@Serializable +private data class TabData( + val originalUrl: String? = null, + val url: String? = null, + val id: String? = null, + val contentFilters: List? = null, + val sortFilter: String? = null, +) + +fun ListLinkHandler.toTabDataString() = JsonHelper.json.encodeToString( + TabData(originalUrl, url, id, contentFilters, sortFilter) +) + +fun String.toListLinkHandler() = with(JsonHelper.json.decodeFromString(this)) { + ListLinkHandler(originalUrl, url, id, contentFilters, sortFilter) +} + +class NewPipeMediaServiceRepository : MediaServiceRepository { + override suspend fun getTrending(region: String): List { + val kioskList = NewPipeExtractorInstance.extractor.kioskList + kioskList.forceContentCountry(ContentCountry(region)) + + val extractor = kioskList.defaultKioskExtractor + extractor.fetchPage() + + val info = KioskInfo.getInfo(extractor) + return info.relatedItems.filterIsInstance().map { it.toStreamItem() } + } + + override suspend fun getStreams(videoId: String): Streams = withContext(Dispatchers.IO) { + val respAsync = async { + StreamInfo.getInfo("$YOUTUBE_FRONTEND_URL/watch?v=$videoId") + } + val dislikesAsync = async { + if (PlayerHelper.localRYD) runCatching { + RetrofitInstance.externalApi.getVotes(videoId).dislikes + }.getOrElse { -1 } else -1 + } + val (resp, dislikes) = Pair(respAsync.await(), dislikesAsync.await()) + + Streams( + title = resp.name, + description = resp.description.content, + uploader = resp.uploaderName, + uploaderAvatar = resp.uploaderAvatars.maxBy { it.height }.url, + uploaderUrl = resp.uploaderUrl.toID(), + uploaderVerified = resp.isUploaderVerified, + uploaderSubscriberCount = resp.uploaderSubscriberCount, + category = resp.category, + views = resp.viewCount, + likes = resp.likeCount, + dislikes = dislikes, + license = resp.licence, + hls = resp.hlsUrl, + dash = resp.dashMpdUrl, + tags = resp.tags, + metaInfo = resp.metaInfo.map { + MetaInfo( + it.title, + it.content.content, + it.urls.map { url -> url.toString() }, + it.urlTexts + ) + }, + visibility = resp.privacy.name.lowercase(), + duration = resp.duration, + uploadTimestamp = resp.uploadDate.offsetDateTime().toInstant().toKotlinInstant(), + uploaded = resp.uploadDate.offsetDateTime().toEpochSecond() * 1000, + thumbnailUrl = resp.thumbnails.maxBy { it.height }.url, + relatedStreams = resp.relatedItems + .filterIsInstance() + .map { item -> item.toStreamItem() }, + chapters = resp.streamSegments.map { + ChapterSegment( + title = it.title, + image = it.previewUrl.orEmpty(), + start = it.startTimeSeconds.toLong() + ) + }, + audioStreams = resp.audioStreams.map { it.toPipedStream() }, + videoStreams = resp.videoOnlyStreams.map { it.toPipedStream().copy(videoOnly = true) } + + resp.videoStreams.map { it.toPipedStream().copy(videoOnly = false) }, + previewFrames = resp.previewFrames.map { + PreviewFrames( + it.urls, + it.frameWidth, + it.frameHeight, + it.totalCount, + it.durationPerFrame.toLong(), + it.framesPerPageX, + it.framesPerPageY + ) + }, + subtitles = resp.subtitles.map { + Subtitle( + it.content, + it.format?.mimeType, + it.displayLanguageName, + it.languageTag, + it.isAutoGenerated + ) + } + ) + } + + override suspend fun getSegments( + videoId: String, + category: String, + actionType: String? + ): SegmentData = SegmentData() + + override suspend fun getDeArrowContent(videoIds: String): Map = + emptyMap() + + override suspend fun getSearchResults(searchQuery: String, filter: String): SearchResult { + val queryHandler = NewPipeExtractorInstance.extractor.searchQHFactory.fromQuery( + searchQuery, + listOf(filter), + null + ) + val searchInfo = SearchInfo.getInfo(NewPipeExtractorInstance.extractor, queryHandler) + + return SearchResult( + items = searchInfo.relatedItems.mapNotNull { it.toContentItem() }, + nextpage = searchInfo.nextPage?.toNextPageString() + ) + } + + override suspend fun getSearchResultsNextPage( + searchQuery: String, + filter: String, + nextPage: String + ): SearchResult { + val queryHandler = NewPipeExtractorInstance.extractor.searchQHFactory.fromQuery( + searchQuery, + listOf(filter), + null + ) + val searchInfo = SearchInfo.getMoreItems( + NewPipeExtractorInstance.extractor, + queryHandler, + nextPage.toPage() + ) + return SearchResult( + items = searchInfo.items.mapNotNull { it.toContentItem() }, + nextpage = searchInfo.nextPage?.toNextPageString() + ) + } + + override suspend fun getSuggestions(query: String): List { + return NewPipeExtractorInstance.extractor.suggestionExtractor.suggestionList(query) + } + + private suspend fun getLatestVideos(channelInfo: ChannelInfo): Pair, String?> { + val relatedTab = channelInfo.tabs.find { it.contentFilters.contains(ChannelTabs.VIDEOS) } + if (relatedTab != null) { + val relatedStreamsResp = getChannelTab(relatedTab.toTabDataString()) + return relatedStreamsResp.content.map { it.toStreamItem() } to relatedStreamsResp.nextpage + } + + return emptyList() to null + } + + override suspend fun getChannel(channelId: String): Channel { + val channelUrl = "$YOUTUBE_FRONTEND_URL/channel/${channelId}" + val channelInfo = ChannelInfo.getInfo(NewPipeExtractorInstance.extractor, channelUrl) + + val channel = channelInfo.toChannel() + + val relatedVideos = getLatestVideos(channelInfo) + channel.relatedStreams = relatedVideos.first + channel.nextpage = relatedVideos.second + + return channel + } + + override suspend fun getChannelTab(data: String, nextPage: String?): ChannelTabResponse { + val linkListHandler = data.toListLinkHandler() + + val resp = ChannelTabInfo.getInfo(NewPipeExtractorInstance.extractor, linkListHandler) + val newNextPage = resp.nextPage?.toNextPageString() + + val items = resp.relatedItems + .mapNotNull { it.toContentItem() } + return ChannelTabResponse(items, newNextPage) + } + + override suspend fun getChannelByName(channelName: String): Channel { + val channelUrl = "$YOUTUBE_FRONTEND_URL/c/${channelName}" + val channelInfo = ChannelInfo.getInfo(NewPipeExtractorInstance.extractor, channelUrl) + + val channel = channelInfo.toChannel() + + val relatedVideos = getLatestVideos(channelInfo) + channel.relatedStreams = relatedVideos.first + channel.nextpage = relatedVideos.second + + return channel + } + + override suspend fun getChannelNextPage(channelId: String, nextPage: String): Channel { + val url = "${YOUTUBE_FRONTEND_URL}/channel/${channelId}/videos" + val listLinkHandler = ListLinkHandler(url, url, channelId, listOf("videos"), "") + val tab = getChannelTab(listLinkHandler.toTabDataString(), nextPage) + return Channel( + relatedStreams = tab.content.map { it.toStreamItem() }, + nextpage = tab.nextpage + ) + } + + override suspend fun getPlaylist(playlistId: String): Playlist { + val playlistUrl = "${YOUTUBE_FRONTEND_URL}/playlist?list=${playlistId}" + val playlistInfo = PlaylistInfo.getInfo(playlistUrl) + + return playlistInfo.toPlaylist() + } + + override suspend fun getPlaylistNextPage(playlistId: String, nextPage: String): Playlist { + val playlistUrl = "${YOUTUBE_FRONTEND_URL}/playlist?list=${playlistId}" + val playlistInfo = PlaylistInfo.getMoreItems( + NewPipeExtractorInstance.extractor, + playlistUrl, + nextPage.toPage() + ) + + return Playlist( + relatedStreams = playlistInfo.items.map { it.toStreamItem() }, + nextpage = playlistInfo.nextPage?.toNextPageString() + ) + } + + override suspend fun getComments(videoId: String): CommentsPage { + val url = "${YOUTUBE_FRONTEND_URL}/watch?v=$videoId" + val commentsInfo = CommentsInfo.getInfo(url) + + return CommentsPage( + nextpage = commentsInfo.nextPage?.toNextPageString(), + disabled = commentsInfo.isCommentsDisabled, + commentCount = commentsInfo.commentsCount.toLong(), + comments = commentsInfo.relatedItems.map { it.toComment() } + ) + } + + override suspend fun getCommentsNextPage(videoId: String, nextPage: String): CommentsPage { + val url = "${YOUTUBE_FRONTEND_URL}/watch?v=$videoId" + val commentsInfo = CommentsInfo.getMoreItems( + NewPipeExtractorInstance.extractor, + url, + nextPage.toPage() + ) + + return CommentsPage( + nextpage = commentsInfo.nextPage?.toNextPageString(), + comments = commentsInfo.items.map { it.toComment() } + ) + } +} diff --git a/app/src/main/java/com/github/libretube/api/PipedMediaServiceRepository.kt b/app/src/main/java/com/github/libretube/api/PipedMediaServiceRepository.kt index a7aef9d72..c1b9c1a92 100644 --- a/app/src/main/java/com/github/libretube/api/PipedMediaServiceRepository.kt +++ b/app/src/main/java/com/github/libretube/api/PipedMediaServiceRepository.kt @@ -5,6 +5,7 @@ import com.github.libretube.api.obj.Channel import com.github.libretube.api.obj.ChannelTabResponse import com.github.libretube.api.obj.CommentsPage import com.github.libretube.api.obj.DeArrowContent +import com.github.libretube.api.obj.Message import com.github.libretube.api.obj.Playlist import com.github.libretube.api.obj.SearchResult import com.github.libretube.api.obj.SegmentData @@ -12,13 +13,23 @@ import com.github.libretube.api.obj.StreamItem import com.github.libretube.api.obj.Streams import com.github.libretube.constants.PreferenceKeys import com.github.libretube.helpers.PreferenceHelper +import retrofit2.HttpException -class PipedMediaServiceRepository : MediaServiceRepository { +open class PipedMediaServiceRepository : MediaServiceRepository { override suspend fun getTrending(region: String): List = api.getTrending(region) - override suspend fun getStreams(videoId: String): Streams = - api.getStreams(videoId) + override suspend fun getStreams(videoId: String): Streams { + return try { + api.getStreams(videoId) + } catch (e: HttpException) { + val errorMessage = e.response()?.errorBody()?.string()?.runCatching { + JsonHelper.json.decodeFromString(this).message + }?.getOrNull() + + throw Exception(errorMessage) + } + } override suspend fun getComments(videoId: String): CommentsPage = api.getComments(videoId) diff --git a/app/src/main/java/com/github/libretube/api/StreamsExtractor.kt b/app/src/main/java/com/github/libretube/api/StreamsExtractor.kt deleted file mode 100644 index 1436decb3..000000000 --- a/app/src/main/java/com/github/libretube/api/StreamsExtractor.kt +++ /dev/null @@ -1,176 +0,0 @@ -package com.github.libretube.api - -import android.content.Context -import com.github.libretube.R -import com.github.libretube.api.obj.ChapterSegment -import com.github.libretube.api.obj.Message -import com.github.libretube.api.obj.MetaInfo -import com.github.libretube.api.obj.PipedStream -import com.github.libretube.api.obj.PreviewFrames -import com.github.libretube.api.obj.StreamItem -import com.github.libretube.api.obj.Streams -import com.github.libretube.api.obj.Subtitle -import com.github.libretube.extensions.toID -import com.github.libretube.helpers.PlayerHelper -import com.github.libretube.ui.dialogs.ShareDialog.Companion.YOUTUBE_FRONTEND_URL -import com.github.libretube.util.deArrow -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.async -import kotlinx.coroutines.withContext -import kotlinx.datetime.toKotlinInstant -import org.schabi.newpipe.extractor.stream.AudioStream -import org.schabi.newpipe.extractor.stream.StreamInfo -import org.schabi.newpipe.extractor.stream.StreamInfoItem -import org.schabi.newpipe.extractor.stream.VideoStream -import retrofit2.HttpException -import java.io.IOException - -fun VideoStream.toPipedStream() = PipedStream( - url = content, - codec = codec, - format = format?.toString(), - height = height, - width = width, - quality = getResolution(), - mimeType = format?.mimeType, - bitrate = bitrate, - initStart = initStart, - initEnd = initEnd, - indexStart = indexStart, - indexEnd = indexEnd, - fps = fps, - contentLength = itagItem?.contentLength ?: 0L -) - -fun AudioStream.toPipedStream() = PipedStream( - url = content, - format = format?.toString(), - quality = "$averageBitrate bits", - bitrate = bitrate, - mimeType = format?.mimeType, - initStart = initStart, - initEnd = initEnd, - indexStart = indexStart, - indexEnd = indexEnd, - contentLength = itagItem?.contentLength ?: 0L, - codec = codec, - audioTrackId = audioTrackId, - audioTrackName = audioTrackName, - audioTrackLocale = audioLocale?.toLanguageTag(), - audioTrackType = audioTrackType?.name, - videoOnly = false -) - -fun StreamInfoItem.toStreamItem( - uploaderAvatarUrl: String? = null -) = StreamItem( - type = StreamItem.TYPE_STREAM, - url = url.toID(), - title = name, - uploaded = uploadDate?.offsetDateTime()?.toEpochSecond()?.times(1000) ?: -1, - uploadedDate = textualUploadDate ?: uploadDate?.offsetDateTime()?.toLocalDateTime() - ?.toLocalDate() - ?.toString(), - uploaderName = uploaderName, - uploaderUrl = uploaderUrl.toID(), - uploaderAvatar = uploaderAvatarUrl ?: uploaderAvatars.maxByOrNull { it.height }?.url, - thumbnail = thumbnails.maxByOrNull { it.height }?.url, - duration = duration, - views = viewCount, - uploaderVerified = isUploaderVerified, - shortDescription = shortDescription, - isShort = isShortFormContent -) - -object StreamsExtractor { - suspend fun extractStreams(videoId: String): Streams = withContext(Dispatchers.IO) { - if (!PlayerHelper.disablePipedProxy || !PlayerHelper.localStreamExtraction) { - return@withContext MediaServiceRepository.instance.getStreams(videoId).deArrow(videoId) - } - - val respAsync = async { - StreamInfo.getInfo("$YOUTUBE_FRONTEND_URL/watch?v=$videoId") - } - val dislikesAsync = async { - if (PlayerHelper.localRYD) runCatching { - RetrofitInstance.externalApi.getVotes(videoId).dislikes - }.getOrElse { -1 } else -1 - } - val (resp, dislikes) = Pair(respAsync.await(), dislikesAsync.await()) - - Streams( - title = resp.name, - description = resp.description.content, - uploader = resp.uploaderName, - uploaderAvatar = resp.uploaderAvatars.maxBy { it.height }.url, - uploaderUrl = resp.uploaderUrl.toID(), - uploaderVerified = resp.isUploaderVerified, - uploaderSubscriberCount = resp.uploaderSubscriberCount, - category = resp.category, - views = resp.viewCount, - likes = resp.likeCount, - dislikes = dislikes, - license = resp.licence, - hls = resp.hlsUrl, - dash = resp.dashMpdUrl, - tags = resp.tags, - metaInfo = resp.metaInfo.map { - MetaInfo( - it.title, - it.content.content, - it.urls.map { url -> url.toString() }, - it.urlTexts - ) - }, - visibility = resp.privacy.name.lowercase(), - duration = resp.duration, - uploadTimestamp = resp.uploadDate.offsetDateTime().toInstant().toKotlinInstant(), - uploaded = resp.uploadDate.offsetDateTime().toEpochSecond() * 1000, - thumbnailUrl = resp.thumbnails.maxBy { it.height }.url, - relatedStreams = resp.relatedItems - .filterIsInstance() - .map { item -> item.toStreamItem() }, - chapters = resp.streamSegments.map { - ChapterSegment( - title = it.title, - image = it.previewUrl.orEmpty(), - start = it.startTimeSeconds.toLong() - ) - }, - audioStreams = resp.audioStreams.map { it.toPipedStream() }, - videoStreams = resp.videoOnlyStreams.map { it.toPipedStream().copy(videoOnly = true) } + - resp.videoStreams.map { it.toPipedStream().copy(videoOnly = false) }, - previewFrames = resp.previewFrames.map { - PreviewFrames( - it.urls, - it.frameWidth, - it.frameHeight, - it.totalCount, - it.durationPerFrame.toLong(), - it.framesPerPageX, - it.framesPerPageY - ) - }, - subtitles = resp.subtitles.map { - Subtitle( - it.content, - it.format?.mimeType, - it.displayLanguageName, - it.languageTag, - it.isAutoGenerated - ) - } - ).deArrow(videoId) - } - - fun getExtractorErrorMessageString(context: Context, exception: Exception): String { - return when (exception) { - is IOException -> context.getString(R.string.unknown_error) - is HttpException -> exception.response()?.errorBody()?.string()?.runCatching { - JsonHelper.json.decodeFromString(this).message - }?.getOrNull() ?: context.getString(R.string.server_error) - - else -> exception.localizedMessage.orEmpty() - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/github/libretube/api/obj/Channel.kt b/app/src/main/java/com/github/libretube/api/obj/Channel.kt index db7cb3f1e..fcaf1381d 100644 --- a/app/src/main/java/com/github/libretube/api/obj/Channel.kt +++ b/app/src/main/java/com/github/libretube/api/obj/Channel.kt @@ -9,7 +9,7 @@ data class Channel( val avatarUrl: String? = null, val bannerUrl: String? = null, val description: String? = null, - val nextpage: String? = null, + var nextpage: String? = null, val subscriberCount: Long = 0, val verified: Boolean = false, var relatedStreams: List = emptyList(), diff --git a/app/src/main/java/com/github/libretube/api/obj/SearchResult.kt b/app/src/main/java/com/github/libretube/api/obj/SearchResult.kt index 723fff9bb..06370fb59 100644 --- a/app/src/main/java/com/github/libretube/api/obj/SearchResult.kt +++ b/app/src/main/java/com/github/libretube/api/obj/SearchResult.kt @@ -6,6 +6,4 @@ import kotlinx.serialization.Serializable data class SearchResult( var items: List = emptyList(), val nextpage: String? = null, - val suggestion: String? = null, - val corrected: Boolean? = null ) diff --git a/app/src/main/java/com/github/libretube/repo/LocalPlaylistsRepository.kt b/app/src/main/java/com/github/libretube/repo/LocalPlaylistsRepository.kt index ba0d858e8..b53d5ce8c 100644 --- a/app/src/main/java/com/github/libretube/repo/LocalPlaylistsRepository.kt +++ b/app/src/main/java/com/github/libretube/repo/LocalPlaylistsRepository.kt @@ -3,7 +3,6 @@ package com.github.libretube.repo import com.github.libretube.api.MediaServiceRepository import com.github.libretube.api.PlaylistsHelper import com.github.libretube.api.PlaylistsHelper.MAX_CONCURRENT_IMPORT_CALLS -import com.github.libretube.api.StreamsExtractor import com.github.libretube.api.obj.Playlist import com.github.libretube.api.obj.Playlists import com.github.libretube.api.obj.StreamItem @@ -125,7 +124,7 @@ class LocalPlaylistsRepository: PlaylistRepository { // Only do so with `MAX_CONCURRENT_IMPORT_CALLS` videos at once to prevent performance issues for (videoIdList in playlist.videos.chunked(MAX_CONCURRENT_IMPORT_CALLS)) { val streams = videoIdList.parallelMap { - runCatching { StreamsExtractor.extractStreams(it) } + runCatching { MediaServiceRepository.instance.getStreams(it) } .getOrNull() ?.toStreamItem(it) }.filterNotNull() diff --git a/app/src/main/java/com/github/libretube/services/DownloadService.kt b/app/src/main/java/com/github/libretube/services/DownloadService.kt index 0428be429..e7975c3cb 100644 --- a/app/src/main/java/com/github/libretube/services/DownloadService.kt +++ b/app/src/main/java/com/github/libretube/services/DownloadService.kt @@ -20,7 +20,7 @@ import androidx.lifecycle.LifecycleService import androidx.lifecycle.lifecycleScope import com.github.libretube.LibreTubeApp.Companion.DOWNLOAD_CHANNEL_NAME import com.github.libretube.R -import com.github.libretube.api.StreamsExtractor +import com.github.libretube.api.MediaServiceRepository import com.github.libretube.api.obj.Streams import com.github.libretube.constants.IntentData import com.github.libretube.db.DatabaseHolder.Database @@ -124,12 +124,13 @@ class DownloadService : LifecycleService() { lifecycleScope.launch(coroutineContext) { val streams = try { withContext(Dispatchers.IO) { - StreamsExtractor.extractStreams(videoId) + MediaServiceRepository.instance.getStreams(videoId) } + } catch (e: IOException) { + toastFromMainDispatcher(getString(R.string.unknown_error)) + return@launch } catch (e: Exception) { - toastFromMainDispatcher( - StreamsExtractor.getExtractorErrorMessageString(this@DownloadService, e) - ) + toastFromMainDispatcher(e.message ?: getString(R.string.server_error)) return@launch } @@ -443,7 +444,7 @@ class DownloadService : LifecycleService() { */ private suspend fun regenerateLink(item: DownloadItem) { val streams = runCatching { - StreamsExtractor.extractStreams(item.videoId) + MediaServiceRepository.instance.getStreams(item.videoId) }.getOrNull() ?: return val stream = when (item.type) { FileType.AUDIO -> streams.audioStreams diff --git a/app/src/main/java/com/github/libretube/services/OnlinePlayerService.kt b/app/src/main/java/com/github/libretube/services/OnlinePlayerService.kt index 56166588e..e6d002b1f 100644 --- a/app/src/main/java/com/github/libretube/services/OnlinePlayerService.kt +++ b/app/src/main/java/com/github/libretube/services/OnlinePlayerService.kt @@ -13,7 +13,6 @@ import androidx.media3.exoplayer.hls.HlsMediaSource import com.github.libretube.R import com.github.libretube.api.JsonHelper import com.github.libretube.api.MediaServiceRepository -import com.github.libretube.api.StreamsExtractor import com.github.libretube.api.SubscriptionHelper import com.github.libretube.api.obj.Segment import com.github.libretube.api.obj.Streams @@ -37,6 +36,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlinx.serialization.encodeToString +import java.io.IOException /** * Loads the selected videos audio in background mode with a notification area. @@ -119,11 +119,12 @@ open class OnlinePlayerService : AbstractPlayerService() { streams = withContext(Dispatchers.IO) { try { - StreamsExtractor.extractStreams(videoId) + MediaServiceRepository.instance.getStreams(videoId) + } catch (e: IOException) { + toastFromMainDispatcher(getString(R.string.unknown_error)) + return@withContext null } catch (e: Exception) { - val errorMessage = - StreamsExtractor.getExtractorErrorMessageString(this@OnlinePlayerService, e) - this@OnlinePlayerService.toastFromMainDispatcher(errorMessage) + toastFromMainDispatcher(e.message ?: getString(R.string.server_error)) return@withContext null } } ?: return diff --git a/app/src/main/java/com/github/libretube/services/PlaylistDownloadEnqueueService.kt b/app/src/main/java/com/github/libretube/services/PlaylistDownloadEnqueueService.kt index 56e85b25a..d14177ff6 100644 --- a/app/src/main/java/com/github/libretube/services/PlaylistDownloadEnqueueService.kt +++ b/app/src/main/java/com/github/libretube/services/PlaylistDownloadEnqueueService.kt @@ -12,7 +12,6 @@ import com.github.libretube.LibreTubeApp.Companion.PLAYLIST_DOWNLOAD_ENQUEUE_CHA import com.github.libretube.R import com.github.libretube.api.MediaServiceRepository import com.github.libretube.api.PlaylistsHelper -import com.github.libretube.api.StreamsExtractor import com.github.libretube.api.obj.PipedStream import com.github.libretube.api.obj.StreamItem import com.github.libretube.constants.IntentData @@ -137,7 +136,7 @@ class PlaylistDownloadEnqueueService : LifecycleService() { for (stream in streams) { val videoInfo = runCatching { - StreamsExtractor.extractStreams(stream.url!!.toID()) + MediaServiceRepository.instance.getStreams(stream.url!!.toID()) }.getOrNull() ?: continue val videoStream = getStream(videoInfo.videoStreams, maxVideoQuality) diff --git a/app/src/main/java/com/github/libretube/ui/activities/AddToPlaylistActivity.kt b/app/src/main/java/com/github/libretube/ui/activities/AddToPlaylistActivity.kt index d24037ce4..2d8f3b273 100644 --- a/app/src/main/java/com/github/libretube/ui/activities/AddToPlaylistActivity.kt +++ b/app/src/main/java/com/github/libretube/ui/activities/AddToPlaylistActivity.kt @@ -6,7 +6,7 @@ import androidx.core.net.toUri import androidx.core.os.bundleOf import androidx.lifecycle.lifecycleScope import com.github.libretube.R -import com.github.libretube.api.StreamsExtractor +import com.github.libretube.api.MediaServiceRepository import com.github.libretube.api.obj.StreamItem import com.github.libretube.constants.IntentData import com.github.libretube.extensions.toastFromMainDispatcher @@ -41,7 +41,7 @@ class AddToPlaylistActivity : BaseActivity() { lifecycleScope.launch(Dispatchers.IO) { val videoInfo = if (PreferenceHelper.getToken().isEmpty()) { try { - StreamsExtractor.extractStreams(videoId).toStreamItem(videoId) + MediaServiceRepository.instance.getStreams(videoId).toStreamItem(videoId) } catch (e: Exception) { toastFromMainDispatcher(R.string.unknown_error) withContext(Dispatchers.Main) { diff --git a/app/src/main/java/com/github/libretube/ui/adapters/SearchChannelAdapter.kt b/app/src/main/java/com/github/libretube/ui/adapters/SearchChannelAdapter.kt index ad55a6653..8a35f2b4b 100644 --- a/app/src/main/java/com/github/libretube/ui/adapters/SearchChannelAdapter.kt +++ b/app/src/main/java/com/github/libretube/ui/adapters/SearchChannelAdapter.kt @@ -159,12 +159,10 @@ class SearchChannelAdapter : ListAdapter( } root.setOnLongClickListener { - val playlistId = item.url.toID() - val playlistName = item.name!! val sheet = PlaylistOptionsBottomSheet() sheet.arguments = bundleOf( - IntentData.playlistId to playlistId, - IntentData.playlistName to playlistName, + IntentData.playlistId to item.url.toID(), + IntentData.playlistName to item.name.orEmpty(), IntentData.playlistType to PlaylistType.PUBLIC ) sheet.show( diff --git a/app/src/main/java/com/github/libretube/ui/dialogs/DownloadDialog.kt b/app/src/main/java/com/github/libretube/ui/dialogs/DownloadDialog.kt index ddd431c08..1e8865cfd 100644 --- a/app/src/main/java/com/github/libretube/ui/dialogs/DownloadDialog.kt +++ b/app/src/main/java/com/github/libretube/ui/dialogs/DownloadDialog.kt @@ -5,7 +5,6 @@ import android.content.DialogInterface import android.os.Bundle import android.text.InputFilter import android.text.format.Formatter -import android.util.Log import android.widget.Toast import androidx.core.os.bundleOf import androidx.core.view.isGone @@ -13,13 +12,12 @@ import androidx.fragment.app.DialogFragment import androidx.fragment.app.setFragmentResult import androidx.lifecycle.lifecycleScope import com.github.libretube.R -import com.github.libretube.api.StreamsExtractor +import com.github.libretube.api.MediaServiceRepository import com.github.libretube.api.obj.PipedStream import com.github.libretube.api.obj.Streams import com.github.libretube.api.obj.Subtitle import com.github.libretube.constants.IntentData import com.github.libretube.databinding.DialogDownloadBinding -import com.github.libretube.extensions.TAG import com.github.libretube.extensions.getWhileDigit import com.github.libretube.extensions.toastFromMainDispatcher import com.github.libretube.helpers.DownloadHelper @@ -30,6 +28,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import java.io.IOException class DownloadDialog : DialogFragment() { private lateinit var videoId: String @@ -80,13 +79,13 @@ class DownloadDialog : DialogFragment() { lifecycleScope.launch { val response = try { withContext(Dispatchers.IO) { - StreamsExtractor.extractStreams(videoId) + MediaServiceRepository.instance.getStreams(videoId) } + } catch (e: IOException) { + context?.toastFromMainDispatcher(getString(R.string.unknown_error)) + return@launch } catch (e: Exception) { - Log.e(TAG(), e.stackTraceToString()) - val context = context ?: return@launch - val errorMessage = StreamsExtractor.getExtractorErrorMessageString(context, e) - context.toastFromMainDispatcher(errorMessage) + context?.toastFromMainDispatcher(e.message ?: getString(R.string.server_error)) return@launch } initDownloadOptions(binding, response) diff --git a/app/src/main/java/com/github/libretube/ui/models/sources/CommentPagingSource.kt b/app/src/main/java/com/github/libretube/ui/models/sources/CommentPagingSource.kt index abba56dfc..38f2a8298 100644 --- a/app/src/main/java/com/github/libretube/ui/models/sources/CommentPagingSource.kt +++ b/app/src/main/java/com/github/libretube/ui/models/sources/CommentPagingSource.kt @@ -4,6 +4,8 @@ import androidx.paging.PagingSource import androidx.paging.PagingState import com.github.libretube.api.MediaServiceRepository import com.github.libretube.api.obj.Comment +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext class CommentPagingSource( private val videoId: String, @@ -13,9 +15,11 @@ class CommentPagingSource( override suspend fun load(params: LoadParams): LoadResult { return try { - val result = params.key?.let { - MediaServiceRepository.instance.getCommentsNextPage(videoId, it) - } ?: MediaServiceRepository.instance.getComments(videoId) + val result = withContext(Dispatchers.IO) { + params.key?.let { + MediaServiceRepository.instance.getCommentsNextPage(videoId, it) + } ?: MediaServiceRepository.instance.getComments(videoId) + } if (result.commentCount > 0) onCommentCount(result.commentCount) diff --git a/app/src/main/java/com/github/libretube/ui/models/sources/CommentRepliesPagingSource.kt b/app/src/main/java/com/github/libretube/ui/models/sources/CommentRepliesPagingSource.kt index b7725ba46..12af258c2 100644 --- a/app/src/main/java/com/github/libretube/ui/models/sources/CommentRepliesPagingSource.kt +++ b/app/src/main/java/com/github/libretube/ui/models/sources/CommentRepliesPagingSource.kt @@ -4,6 +4,8 @@ import androidx.paging.PagingSource import androidx.paging.PagingState import com.github.libretube.api.MediaServiceRepository import com.github.libretube.api.obj.Comment +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext class CommentRepliesPagingSource( private val videoId: String, @@ -14,7 +16,9 @@ class CommentRepliesPagingSource( override suspend fun load(params: LoadParams): LoadResult { return try { val key = params.key.orEmpty().ifEmpty { originalComment.repliesPage.orEmpty() } - val result = MediaServiceRepository.instance.getCommentsNextPage(videoId, key) + val result = withContext(Dispatchers.IO) { + MediaServiceRepository.instance.getCommentsNextPage(videoId, key) + } val replies = result.comments.toMutableList() if (params.key.isNullOrEmpty()) { diff --git a/app/src/main/java/com/github/libretube/ui/models/sources/SearchPagingSource.kt b/app/src/main/java/com/github/libretube/ui/models/sources/SearchPagingSource.kt index d2c0d283a..9390c2c54 100644 --- a/app/src/main/java/com/github/libretube/ui/models/sources/SearchPagingSource.kt +++ b/app/src/main/java/com/github/libretube/ui/models/sources/SearchPagingSource.kt @@ -5,6 +5,8 @@ import androidx.paging.PagingState import com.github.libretube.api.MediaServiceRepository import com.github.libretube.api.obj.ContentItem import com.github.libretube.util.deArrow +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext class SearchPagingSource( private val searchQuery: String, @@ -14,9 +16,14 @@ class SearchPagingSource( override suspend fun load(params: LoadParams): LoadResult { return try { - val result = params.key?.let { - MediaServiceRepository.instance.getSearchResultsNextPage(searchQuery, searchFilter, it) - } ?: MediaServiceRepository.instance.getSearchResults(searchQuery, searchFilter) + val result = withContext(Dispatchers.IO) { + params.key?.let { + MediaServiceRepository.instance.getSearchResultsNextPage( + searchQuery, searchFilter, it + ) + } ?: MediaServiceRepository.instance.getSearchResults(searchQuery, searchFilter) + } + LoadResult.Page(result.items.deArrow(), null, result.nextpage) } catch (e: Exception) { LoadResult.Error(e) diff --git a/app/src/main/java/com/github/libretube/util/DeArrowUtil.kt b/app/src/main/java/com/github/libretube/util/DeArrowUtil.kt index 866990d37..310b58c1d 100644 --- a/app/src/main/java/com/github/libretube/util/DeArrowUtil.kt +++ b/app/src/main/java/com/github/libretube/util/DeArrowUtil.kt @@ -5,6 +5,7 @@ import com.github.libretube.api.MediaServiceRepository import com.github.libretube.api.obj.ContentItem import com.github.libretube.api.obj.DeArrowContent import com.github.libretube.api.obj.StreamItem +import com.github.libretube.api.obj.StreamItem.Companion.TYPE_STREAM import com.github.libretube.api.obj.Streams import com.github.libretube.constants.PreferenceKeys import com.github.libretube.extensions.toID @@ -89,7 +90,7 @@ object DeArrowUtil { if (!PreferenceHelper.getBoolean(PreferenceKeys.DEARROW, false)) return contentItems val videoIds = contentItems - .filter { it.type == "stream" } + .filter { it.type == TYPE_STREAM } .map { it.url.toID() } if (videoIds.isEmpty()) return contentItems diff --git a/app/src/main/java/com/github/libretube/util/PlayingQueue.kt b/app/src/main/java/com/github/libretube/util/PlayingQueue.kt index bd2a3b97a..485a2a924 100644 --- a/app/src/main/java/com/github/libretube/util/PlayingQueue.kt +++ b/app/src/main/java/com/github/libretube/util/PlayingQueue.kt @@ -3,8 +3,6 @@ package com.github.libretube.util import androidx.media3.common.Player import com.github.libretube.api.MediaServiceRepository import com.github.libretube.api.PlaylistsHelper -import com.github.libretube.api.RetrofitInstance -import com.github.libretube.api.StreamsExtractor import com.github.libretube.api.obj.StreamItem import com.github.libretube.extensions.move import com.github.libretube.extensions.runCatchingIO @@ -194,7 +192,7 @@ object PlayingQueue { }.let { queueJobs.add(it) } fun insertByVideoId(videoId: String) = runCatchingIO { - val streams = StreamsExtractor.extractStreams(videoId.toID()) + val streams = MediaServiceRepository.instance.getStreams(videoId.toID()) add(streams.toStreamItem(videoId)) } From 676c62a7e04c290b1063b8c6f99ac8953ae85594 Mon Sep 17 00:00:00 2001 From: Bnyro Date: Sun, 2 Mar 2025 12:42:30 +0100 Subject: [PATCH 3/3] feat: add full local mode option to instance settings --- .../libretube/api/MediaServiceRepository.kt | 12 ++- .../libretube/constants/PreferenceKeys.kt | 1 + .../github/libretube/helpers/PlayerHelper.kt | 6 ++ app/src/main/res/values/strings.xml | 4 + app/src/main/res/xml/instance_settings.xml | 90 +++++++++++-------- 5 files changed, 67 insertions(+), 46 deletions(-) diff --git a/app/src/main/java/com/github/libretube/api/MediaServiceRepository.kt b/app/src/main/java/com/github/libretube/api/MediaServiceRepository.kt index 9212a5136..c3123e2ae 100644 --- a/app/src/main/java/com/github/libretube/api/MediaServiceRepository.kt +++ b/app/src/main/java/com/github/libretube/api/MediaServiceRepository.kt @@ -39,13 +39,11 @@ interface MediaServiceRepository { suspend fun getPlaylistNextPage(playlistId: String, nextPage: String): Playlist companion object { - val instance by lazy { - if (PlayerHelper.disablePipedProxy && PlayerHelper.localStreamExtraction) { - // TODO: LocalStreamsExtractionPipedMediaServiceRepository() - NewPipeMediaServiceRepository() - } else { - PipedMediaServiceRepository() + val instance: MediaServiceRepository + get() = when { + PlayerHelper.fullLocalMode -> NewPipeMediaServiceRepository() + PlayerHelper.localStreamExtraction -> LocalStreamsExtractionPipedMediaServiceRepository() + else -> PipedMediaServiceRepository() } - } } } \ No newline at end of file diff --git a/app/src/main/java/com/github/libretube/constants/PreferenceKeys.kt b/app/src/main/java/com/github/libretube/constants/PreferenceKeys.kt index 04ee5ef80..48be21253 100644 --- a/app/src/main/java/com/github/libretube/constants/PreferenceKeys.kt +++ b/app/src/main/java/com/github/libretube/constants/PreferenceKeys.kt @@ -133,6 +133,7 @@ object PreferenceKeys { const val MAX_CONCURRENT_DOWNLOADS = "max_parallel_downloads" const val EXTERNAL_DOWNLOAD_PROVIDER = "external_download_provider" const val DISABLE_VIDEO_IMAGE_PROXY = "disable_video_image_proxy" + const val FULL_LOCAL_MODE = "full_local_mode" const val LOCAL_RYD = "local_return_youtube_dislikes" const val LOCAL_STREAM_EXTRACTION = "local_stream_extraction" diff --git a/app/src/main/java/com/github/libretube/helpers/PlayerHelper.kt b/app/src/main/java/com/github/libretube/helpers/PlayerHelper.kt index 0969e1a01..fce7521de 100644 --- a/app/src/main/java/com/github/libretube/helpers/PlayerHelper.kt +++ b/app/src/main/java/com/github/libretube/helpers/PlayerHelper.kt @@ -357,6 +357,12 @@ object PlayerHelper { false ) + val fullLocalMode: Boolean + get() = PreferenceHelper.getBoolean( + PreferenceKeys.FULL_LOCAL_MODE, + false + ) + val localStreamExtraction: Boolean get() = PreferenceHelper.getBoolean( PreferenceKeys.LOCAL_STREAM_EXTRACTION, diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9253d5048..8821649c1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -472,6 +472,7 @@ Crashlog Never show this again Update information + Mode of operation Import subscriptions from @@ -532,6 +533,9 @@ Directly fetch the feed from YouTube. This may be significantly slower. Show upcoming videos Updating feed … + Full local mode + Directly fetch everything from YouTube, without using Piped. + Authentication Download Service diff --git a/app/src/main/res/xml/instance_settings.xml b/app/src/main/res/xml/instance_settings.xml index 4e5a14562..fd70d0c5e 100644 --- a/app/src/main/res/xml/instance_settings.xml +++ b/app/src/main/res/xml/instance_settings.xml @@ -2,6 +2,26 @@ + + + + + + + + + + + + + + + + - - - - - - - - - - - + + android:icon="@drawable/ic_list" + android:summary="@string/hls_instead_of_dash_summary" + android:title="@string/hls_instead_of_dash" + app:key="use_hls" /> - + + + - - - - - - \ No newline at end of file