mirror of
https://github.com/libre-tube/LibreTube.git
synced 2024-12-12 21:30:30 +05:30
Merge AGP_8.0 with upstream and fix some deprecations
This commit is contained in:
commit
cd12dbd7db
7
.github/uploader.py
vendored
7
.github/uploader.py
vendored
@ -1,6 +1,11 @@
|
||||
from os import system as run, listdir, remove
|
||||
from json import load
|
||||
import tgconfig
|
||||
|
||||
with open("../.github/commit.json") as f:
|
||||
data = load(f)
|
||||
|
||||
message = f"Commit {data['sha'][0:7]}, signed off by: {data['commit']['author']['name']}"
|
||||
|
||||
files, signed_files, unsigned_files = listdir(), [], []
|
||||
for file in files:
|
||||
@ -15,7 +20,7 @@ if len(signed_files):
|
||||
|
||||
if tgconfig.GH_REPO.lower() == "libre-tube/libretube":
|
||||
run("git add -f *")
|
||||
run('git commit -m "WORKFLOW: ALPHA BUILDS"')
|
||||
run(f"git commit -m \"{message}\"")
|
||||
run("git push -u")
|
||||
else:
|
||||
print("Official Repo not Detected")
|
||||
|
18
.github/workflows/ci.yml
vendored
18
.github/workflows/ci.yml
vendored
@ -42,10 +42,10 @@ jobs:
|
||||
|
||||
- name: Git Configuraion
|
||||
run: |
|
||||
git config --global user.name "alefvanoon"
|
||||
git config --global user.email "53198048+alefvanoon@users.noreply.github.com"
|
||||
git config --global user.name "Bnyro"
|
||||
git config --global user.email "bnyro@tutanota.com"
|
||||
git config --global credential.helper store
|
||||
echo "https://alefvanoon:${{ secrets.GH_TOKEN }}@github.com" > ~/.git-credentials
|
||||
echo "https://bnyro:${{ secrets.GH_TOKEN }}@github.com" > ~/.git-credentials
|
||||
|
||||
- name: Compile
|
||||
run: |
|
||||
@ -68,16 +68,16 @@ jobs:
|
||||
run: |
|
||||
mv .github/uploader.py .
|
||||
echo "GH_REPO = '${{ github.repository }}'" > tgconfig.py
|
||||
git clone --filter=blob:none https://github.com/LibreTubeAlpha/Archive archive
|
||||
rm -rf archive/*.apk
|
||||
mv app/build/outputs/apk/debug/*.apk archive/
|
||||
cd archive
|
||||
git clone --filter=blob:none https://github.com/libre-tube/NightlyBuilds nightly
|
||||
rm -rf nightly/*.apk
|
||||
mv app/build/outputs/apk/debug/*.apk nightly/
|
||||
cd nightly
|
||||
python ../uploader.py
|
||||
|
||||
- name: Telegram Bot
|
||||
continue-on-error: true
|
||||
run: |
|
||||
cd archive
|
||||
cd nightly
|
||||
mv ../tgconfig.py .
|
||||
echo "TG_TOKEN = '${{ secrets.TG_TOKEN }}'" >> tgconfig.py
|
||||
echo "TG_API_ID = '${{ secrets.TG_API_ID }}'" >> tgconfig.py
|
||||
@ -92,5 +92,5 @@ jobs:
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: app
|
||||
path: archive/*.apk
|
||||
path: nightly/*.apk
|
||||
|
||||
|
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@ -11,7 +11,7 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- name: ktlint
|
||||
uses: ScaCap/action-ktlint@v1.6
|
||||
uses: ScaCap/action-ktlint@v1.7
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
reporter: github-pr-check
|
||||
|
11
README.md
11
README.md
@ -1,12 +1,17 @@
|
||||
<div align="center">
|
||||
<img src="https://libre-tube.github.io/images/gh-banner.png" width="auto" height="auto" alt="LibreTube">
|
||||
|
||||
|
||||
[![GPL-v3](https://libre-tube.github.io/assets/widgets/license-widget.svg)](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||
</div><div align="center" style="width:100%; display:flex; justify-content:space-between;">
|
||||
|
||||
[![Matrix](https://libre-tube.github.io/assets/widgets/mat-widget.svg)](https://matrix.to/#/#LibreTube:matrix.org)
|
||||
[![Mastodon](https://libre-tube.github.io/assets/widgets/mast-widget.svg)](https://fosstodon.org/@libretube)
|
||||
[![Telegram](https://libre-tube.github.io/assets/widgets/tg-widget.svg)](https://t.me/libretube)
|
||||
[![Reddit](https://libre-tube.github.io/assets/widgets/rd-widget.svg)](https://www.reddit.com/r/Libretube/)
|
||||
[![Discord](https://libre-tube.github.io/assets/widgets/discord-widget.svg)](https://discord.gg/Qc34xCj2GV)
|
||||
|
||||
</div>
|
||||
|
||||
> **Note** <br>
|
||||
> We don't accept feature or bug requests on these platforms. Kindly submit requests only on GitHub.
|
||||
|
||||
</div><div align="center" style="width:100%; display:flex; justify-content:space-between;">
|
||||
|
||||
|
@ -14,8 +14,8 @@ android {
|
||||
applicationId 'com.github.libretube'
|
||||
minSdk 21
|
||||
targetSdk 33
|
||||
versionCode 33
|
||||
versionName '0.14.0'
|
||||
versionCode 34
|
||||
versionName '0.14.1'
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
resValue "string", "app_name", "LibreTube"
|
||||
|
||||
|
@ -49,7 +49,7 @@ class LibreTubeApp : Application() {
|
||||
*/
|
||||
NotificationHelper.enqueueWork(
|
||||
context = this,
|
||||
existingPeriodicWorkPolicy = ExistingPeriodicWorkPolicy.KEEP
|
||||
existingPeriodicWorkPolicy = ExistingPeriodicWorkPolicy.KEEP,
|
||||
)
|
||||
|
||||
/**
|
||||
@ -76,21 +76,21 @@ class LibreTubeApp : Application() {
|
||||
private fun initializeNotificationChannels() {
|
||||
val downloadChannel = NotificationChannelCompat.Builder(
|
||||
DOWNLOAD_CHANNEL_ID,
|
||||
NotificationManagerCompat.IMPORTANCE_LOW
|
||||
NotificationManagerCompat.IMPORTANCE_LOW,
|
||||
)
|
||||
.setName(getString(R.string.download_channel_name))
|
||||
.setDescription(getString(R.string.download_channel_description))
|
||||
.build()
|
||||
val backgroundChannel = NotificationChannelCompat.Builder(
|
||||
BACKGROUND_CHANNEL_ID,
|
||||
NotificationManagerCompat.IMPORTANCE_LOW
|
||||
NotificationManagerCompat.IMPORTANCE_LOW,
|
||||
)
|
||||
.setName(getString(R.string.background_channel_name))
|
||||
.setDescription(getString(R.string.background_channel_description))
|
||||
.build()
|
||||
val pushChannel = NotificationChannelCompat.Builder(
|
||||
PUSH_CHANNEL_ID,
|
||||
NotificationManagerCompat.IMPORTANCE_DEFAULT
|
||||
NotificationManagerCompat.IMPORTANCE_DEFAULT,
|
||||
)
|
||||
.setName(getString(R.string.push_channel_name))
|
||||
.setDescription(getString(R.string.push_channel_description))
|
||||
@ -101,8 +101,8 @@ class LibreTubeApp : Application() {
|
||||
listOf(
|
||||
downloadChannel,
|
||||
backgroundChannel,
|
||||
pushChannel
|
||||
)
|
||||
pushChannel,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -41,26 +41,26 @@ interface PipedApi {
|
||||
@GET("sponsors/{videoId}")
|
||||
suspend fun getSegments(
|
||||
@Path("videoId") videoId: String,
|
||||
@Query("category") category: String
|
||||
@Query("category") category: String,
|
||||
): SegmentData
|
||||
|
||||
@GET("nextpage/comments/{videoId}")
|
||||
suspend fun getCommentsNextPage(
|
||||
@Path("videoId") videoId: String,
|
||||
@Query("nextpage") nextPage: String
|
||||
@Query("nextpage") nextPage: String,
|
||||
): CommentsPage
|
||||
|
||||
@GET("search")
|
||||
suspend fun getSearchResults(
|
||||
@Query("q") searchQuery: String,
|
||||
@Query("filter") filter: String
|
||||
@Query("filter") filter: String,
|
||||
): SearchResult
|
||||
|
||||
@GET("nextpage/search")
|
||||
suspend fun getSearchResultsNextPage(
|
||||
@Query("q") searchQuery: String,
|
||||
@Query("filter") filter: String,
|
||||
@Query("nextpage") nextPage: String
|
||||
@Query("nextpage") nextPage: String,
|
||||
): SearchResult
|
||||
|
||||
@GET("suggestions")
|
||||
@ -72,7 +72,7 @@ interface PipedApi {
|
||||
@GET("channels/tabs")
|
||||
suspend fun getChannelTab(
|
||||
@Query("data") data: String,
|
||||
@Query("nextpage") nextPage: String? = null
|
||||
@Query("nextpage") nextPage: String? = null,
|
||||
): ChannelTabResponse
|
||||
|
||||
@GET("user/{name}")
|
||||
@ -81,7 +81,7 @@ interface PipedApi {
|
||||
@GET("nextpage/channel/{channelId}")
|
||||
suspend fun getChannelNextPage(
|
||||
@Path("channelId") channelId: String,
|
||||
@Query("nextpage") nextPage: String
|
||||
@Query("nextpage") nextPage: String,
|
||||
): Channel
|
||||
|
||||
@GET("playlists/{playlistId}")
|
||||
@ -90,7 +90,7 @@ interface PipedApi {
|
||||
@GET("nextpage/playlists/{playlistId}")
|
||||
suspend fun getPlaylistNextPage(
|
||||
@Path("playlistId") playlistId: String,
|
||||
@Query("nextpage") nextPage: String
|
||||
@Query("nextpage") nextPage: String,
|
||||
): Playlist
|
||||
|
||||
@POST("login")
|
||||
@ -102,7 +102,7 @@ interface PipedApi {
|
||||
@POST("user/delete")
|
||||
suspend fun deleteAccount(
|
||||
@Header("Authorization") token: String,
|
||||
@Body password: DeleteUserRequest
|
||||
@Body password: DeleteUserRequest,
|
||||
)
|
||||
|
||||
@GET("feed")
|
||||
@ -110,18 +110,18 @@ interface PipedApi {
|
||||
|
||||
@GET("feed/unauthenticated")
|
||||
suspend fun getUnauthenticatedFeed(
|
||||
@Query("channels") channels: String
|
||||
@Query("channels") channels: String,
|
||||
): List<StreamItem>
|
||||
|
||||
@POST("feed/unauthenticated")
|
||||
suspend fun getUnauthenticatedFeed(
|
||||
@Body channels: List<String>
|
||||
@Body channels: List<String>,
|
||||
): List<StreamItem>
|
||||
|
||||
@GET("subscribed")
|
||||
suspend fun isSubscribed(
|
||||
@Query("channelId") channelId: String,
|
||||
@Header("Authorization") token: String
|
||||
@Header("Authorization") token: String,
|
||||
): Subscribed
|
||||
|
||||
@GET("subscriptions")
|
||||
@ -129,37 +129,37 @@ interface PipedApi {
|
||||
|
||||
@GET("subscriptions/unauthenticated")
|
||||
suspend fun unauthenticatedSubscriptions(
|
||||
@Query("channels") channels: String
|
||||
@Query("channels") channels: String,
|
||||
): List<Subscription>
|
||||
|
||||
@POST("subscriptions/unauthenticated")
|
||||
suspend fun unauthenticatedSubscriptions(
|
||||
@Body channels: List<String>
|
||||
@Body channels: List<String>,
|
||||
): List<Subscription>
|
||||
|
||||
@POST("subscribe")
|
||||
suspend fun subscribe(
|
||||
@Header("Authorization") token: String,
|
||||
@Body subscribe: Subscribe
|
||||
@Body subscribe: Subscribe,
|
||||
): Message
|
||||
|
||||
@POST("unsubscribe")
|
||||
suspend fun unsubscribe(
|
||||
@Header("Authorization") token: String,
|
||||
@Body subscribe: Subscribe
|
||||
@Body subscribe: Subscribe,
|
||||
): Message
|
||||
|
||||
@POST("import")
|
||||
suspend fun importSubscriptions(
|
||||
@Query("override") override: Boolean,
|
||||
@Header("Authorization") token: String,
|
||||
@Body channels: List<String>
|
||||
@Body channels: List<String>,
|
||||
): Message
|
||||
|
||||
@POST("import/playlist")
|
||||
suspend fun clonePlaylist(
|
||||
@Header("Authorization") token: String,
|
||||
@Body playlistId: PlaylistId
|
||||
@Body playlistId: PlaylistId,
|
||||
): PlaylistId
|
||||
|
||||
@GET("user/playlists")
|
||||
@ -168,30 +168,30 @@ interface PipedApi {
|
||||
@POST("user/playlists/rename")
|
||||
suspend fun renamePlaylist(
|
||||
@Header("Authorization") token: String,
|
||||
@Body playlistId: PlaylistId
|
||||
@Body playlistId: PlaylistId,
|
||||
): Message
|
||||
|
||||
@POST("user/playlists/delete")
|
||||
suspend fun deletePlaylist(
|
||||
@Header("Authorization") token: String,
|
||||
@Body playlistId: PlaylistId
|
||||
@Body playlistId: PlaylistId,
|
||||
): Message
|
||||
|
||||
@POST("user/playlists/create")
|
||||
suspend fun createPlaylist(
|
||||
@Header("Authorization") token: String,
|
||||
@Body name: Playlists
|
||||
@Body name: Playlists,
|
||||
): PlaylistId
|
||||
|
||||
@POST("user/playlists/add")
|
||||
suspend fun addToPlaylist(
|
||||
@Header("Authorization") token: String,
|
||||
@Body playlistId: PlaylistId
|
||||
@Body playlistId: PlaylistId,
|
||||
): Message
|
||||
|
||||
@POST("user/playlists/remove")
|
||||
suspend fun removeFromPlaylist(
|
||||
@Header("Authorization") token: String,
|
||||
@Body playlistId: PlaylistId
|
||||
@Body playlistId: PlaylistId,
|
||||
): Message
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
package com.github.libretube.api
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.text.isDigitsOnly
|
||||
import com.github.libretube.api.obj.Playlist
|
||||
import com.github.libretube.api.obj.PlaylistId
|
||||
@ -37,7 +36,7 @@ object PlaylistsHelper {
|
||||
id = it.playlist.id.toString(),
|
||||
name = it.playlist.name,
|
||||
thumbnail = ProxyHelper.rewriteUrl(it.playlist.thumbnailUrl),
|
||||
videos = it.videos.size.toLong()
|
||||
videos = it.videos.size.toLong(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -55,7 +54,7 @@ object PlaylistsHelper {
|
||||
name = relation.playlist.name,
|
||||
thumbnailUrl = ProxyHelper.rewriteUrl(relation.playlist.thumbnailUrl),
|
||||
videos = relation.videos.size,
|
||||
relatedStreams = relation.videos.map { it.toStreamItem() }
|
||||
relatedStreams = relation.videos.map { it.toStreamItem() },
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -118,7 +117,7 @@ object PlaylistsHelper {
|
||||
val transaction = DatabaseHolder.Database.localPlaylistsDao().getAll()
|
||||
.first { it.playlist.id.toString() == playlistId }
|
||||
DatabaseHolder.Database.localPlaylistsDao().removePlaylistVideo(
|
||||
transaction.videos[index]
|
||||
transaction.videos[index],
|
||||
)
|
||||
// set a new playlist thumbnail if the first video got removed
|
||||
if (index == 0) {
|
||||
@ -129,7 +128,7 @@ object PlaylistsHelper {
|
||||
} else {
|
||||
RetrofitInstance.authApi.removeFromPlaylist(
|
||||
PreferenceHelper.getToken(),
|
||||
PlaylistId(playlistId = playlistId, index = index)
|
||||
PlaylistId(playlistId = playlistId, index = index),
|
||||
).message == "ok"
|
||||
}
|
||||
}
|
||||
@ -145,23 +144,24 @@ object PlaylistsHelper {
|
||||
playlistId,
|
||||
*playlist.videos.map {
|
||||
StreamItem(url = it)
|
||||
}.toTypedArray()
|
||||
}.toTypedArray(),
|
||||
)
|
||||
} else {
|
||||
// if not logged in, all video information needs to become fetched manually
|
||||
runCatching {
|
||||
val streamItems = playlist.videos.map {
|
||||
async {
|
||||
runCatching {
|
||||
RetrofitInstance.api.getStreams(it).toStreamItem(it)
|
||||
}.getOrNull()
|
||||
}
|
||||
// Only do so with 20 videos at once to prevent performance issues
|
||||
playlist.videos.mapIndexed { index, id -> id to index }
|
||||
.groupBy { it.second % 20 }.forEach { (_, videos) ->
|
||||
videos.map {
|
||||
async {
|
||||
runCatching {
|
||||
val stream = RetrofitInstance.api.getStreams(it.first).toStreamItem(
|
||||
it.first,
|
||||
)
|
||||
addToPlaylist(playlistId, stream)
|
||||
}
|
||||
}
|
||||
}.awaitAll()
|
||||
}
|
||||
.awaitAll()
|
||||
.filterNotNull()
|
||||
|
||||
addToPlaylist(playlistId, *streamItems.toTypedArray())
|
||||
}
|
||||
}
|
||||
}
|
||||
}.awaitAll()
|
||||
@ -179,8 +179,7 @@ object PlaylistsHelper {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun clonePlaylist(context: Context, playlistId: String): String? {
|
||||
val appContext = context.applicationContext
|
||||
suspend fun clonePlaylist(playlistId: String): String? {
|
||||
if (!loggedIn) {
|
||||
val playlist = RetrofitInstance.api.getPlaylist(playlistId)
|
||||
val newPlaylist = createPlaylist(playlist.name ?: "Unknown name") ?: return null
|
||||
@ -211,7 +210,7 @@ object PlaylistsHelper {
|
||||
return runCatching {
|
||||
RetrofitInstance.authApi.deletePlaylist(
|
||||
PreferenceHelper.getToken(),
|
||||
PlaylistId(playlistId)
|
||||
PlaylistId(playlistId),
|
||||
).message == "ok"
|
||||
}.getOrDefault(false)
|
||||
}
|
||||
|
@ -46,8 +46,7 @@ class ResettableLazy<PROPTYPE>(val manager: ResettableLazyManager, val init: ()
|
||||
}
|
||||
}
|
||||
|
||||
fun <PROPTYPE> resettableLazy(manager: ResettableLazyManager, init: () -> PROPTYPE):
|
||||
ResettableLazy<PROPTYPE> {
|
||||
fun <PROPTYPE> resettableLazy(manager: ResettableLazyManager, init: () -> PROPTYPE): ResettableLazy<PROPTYPE> {
|
||||
return ResettableLazy(manager, init)
|
||||
}
|
||||
|
||||
|
@ -53,12 +53,12 @@ object RetrofitInstance {
|
||||
if (
|
||||
PreferenceHelper.getBoolean(
|
||||
PreferenceKeys.AUTH_INSTANCE_TOGGLE,
|
||||
false
|
||||
false,
|
||||
)
|
||||
) {
|
||||
PreferenceHelper.getString(
|
||||
PreferenceKeys.AUTH_INSTANCE,
|
||||
PIPED_API_URL
|
||||
PIPED_API_URL,
|
||||
)
|
||||
} else {
|
||||
url
|
||||
|
@ -53,7 +53,7 @@ object SubscriptionHelper {
|
||||
context: Context,
|
||||
channelId: String,
|
||||
channelName: String?,
|
||||
onUnsubscribe: () -> Unit
|
||||
onUnsubscribe: () -> Unit,
|
||||
) {
|
||||
if (!PreferenceHelper.getBoolean(PreferenceKeys.CONFIRM_UNSUBSCRIBE, false)) {
|
||||
runBlocking {
|
||||
@ -112,10 +112,10 @@ object SubscriptionHelper {
|
||||
val subscriptions = Database.localSubscriptionDao().getAll().map { it.channelId }
|
||||
when {
|
||||
subscriptions.size > GET_SUBSCRIPTIONS_LIMIT -> RetrofitInstance.authApi.unauthenticatedSubscriptions(
|
||||
subscriptions
|
||||
subscriptions,
|
||||
)
|
||||
else -> RetrofitInstance.authApi.unauthenticatedSubscriptions(
|
||||
subscriptions.joinToString(",")
|
||||
subscriptions.joinToString(","),
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -129,10 +129,10 @@ object SubscriptionHelper {
|
||||
val subscriptions = Database.localSubscriptionDao().getAll().map { it.channelId }
|
||||
when {
|
||||
subscriptions.size > GET_SUBSCRIPTIONS_LIMIT -> RetrofitInstance.authApi.getUnauthenticatedFeed(
|
||||
subscriptions
|
||||
subscriptions,
|
||||
)
|
||||
else -> RetrofitInstance.authApi.getUnauthenticatedFeed(
|
||||
subscriptions.joinToString(",")
|
||||
subscriptions.joinToString(","),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -13,5 +13,5 @@ data class Channel(
|
||||
val subscriberCount: Long = 0,
|
||||
val verified: Boolean = false,
|
||||
val relatedStreams: List<StreamItem> = emptyList(),
|
||||
val tabs: List<ChannelTab> = emptyList()
|
||||
val tabs: List<ChannelTab> = emptyList(),
|
||||
)
|
||||
|
@ -5,5 +5,5 @@ import kotlinx.serialization.Serializable
|
||||
@Serializable
|
||||
data class ChannelTab(
|
||||
val name: String,
|
||||
val data: String
|
||||
val data: String,
|
||||
)
|
||||
|
@ -5,5 +5,5 @@ import kotlinx.serialization.Serializable
|
||||
@Serializable
|
||||
data class ChannelTabResponse(
|
||||
val content: List<ContentItem> = emptyList(),
|
||||
val nextpage: String? = null
|
||||
val nextpage: String? = null,
|
||||
)
|
||||
|
@ -6,5 +6,5 @@ import kotlinx.serialization.Serializable
|
||||
data class ChapterSegment(
|
||||
val title: String,
|
||||
val image: String,
|
||||
val start: Long
|
||||
val start: Long,
|
||||
)
|
||||
|
@ -15,5 +15,5 @@ data class Comment(
|
||||
val pinned: Boolean,
|
||||
val thumbnail: String,
|
||||
val verified: Boolean,
|
||||
val replyCount: Long
|
||||
val replyCount: Long,
|
||||
)
|
||||
|
@ -6,5 +6,5 @@ import kotlinx.serialization.Serializable
|
||||
data class CommentsPage(
|
||||
var comments: List<Comment> = emptyList(),
|
||||
val disabled: Boolean = false,
|
||||
val nextpage: String? = null
|
||||
val nextpage: String? = null,
|
||||
)
|
||||
|
@ -24,5 +24,5 @@ data class ContentItem(
|
||||
val description: String? = null,
|
||||
val subscribers: Long = -1,
|
||||
val videos: Long = -1,
|
||||
val verified: Boolean? = null
|
||||
val verified: Boolean? = null,
|
||||
)
|
||||
|
@ -12,5 +12,5 @@ data class Instances(
|
||||
@SerialName("up_to_date") val upToDate: Boolean = true,
|
||||
val cdn: Boolean = false,
|
||||
val registered: Long = 0,
|
||||
@SerialName("last_checked") val lastChecked: Long = 0
|
||||
@SerialName("last_checked") val lastChecked: Long = 0,
|
||||
)
|
||||
|
@ -5,5 +5,5 @@ import kotlinx.serialization.Serializable
|
||||
@Serializable
|
||||
data class Login(
|
||||
val username: String,
|
||||
val password: String
|
||||
val password: String,
|
||||
)
|
||||
|
@ -5,5 +5,5 @@ import kotlinx.serialization.Serializable
|
||||
@Serializable
|
||||
data class Message(
|
||||
val error: String? = null,
|
||||
val message: String? = null
|
||||
val message: String? = null,
|
||||
)
|
||||
|
@ -6,5 +6,5 @@ import kotlinx.serialization.Serializable
|
||||
data class PipedConfig(
|
||||
val donationUrl: String? = null,
|
||||
val statusPageUrl: String? = null,
|
||||
val imageProxyUrl: String? = null
|
||||
val imageProxyUrl: String? = null,
|
||||
)
|
||||
|
@ -19,7 +19,7 @@ data class PipedStream(
|
||||
val height: Int? = null,
|
||||
val fps: Int? = null,
|
||||
val audioTrackName: String? = null,
|
||||
val audioTrackId: String? = null
|
||||
val audioTrackId: String? = null,
|
||||
) {
|
||||
fun getQualityString(fileName: String): String {
|
||||
return "${fileName}_${quality?.replace(" ", "_")}_$format." +
|
||||
|
@ -13,7 +13,7 @@ data class Playlist(
|
||||
val uploaderUrl: String? = null,
|
||||
val uploaderAvatar: String? = null,
|
||||
val videos: Int = 0,
|
||||
val relatedStreams: List<StreamItem> = emptyList()
|
||||
val relatedStreams: List<StreamItem> = emptyList(),
|
||||
) {
|
||||
fun toPlaylistBookmark(playlistId: String): PlaylistBookmark {
|
||||
return PlaylistBookmark(
|
||||
@ -22,7 +22,7 @@ data class Playlist(
|
||||
thumbnailUrl = thumbnailUrl,
|
||||
uploader = uploader,
|
||||
uploaderAvatar = uploaderAvatar,
|
||||
uploaderUrl = uploaderUrl
|
||||
uploaderUrl = uploaderUrl,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -8,5 +8,5 @@ data class PlaylistId(
|
||||
val videoId: String? = null,
|
||||
val videoIds: List<String> = emptyList(),
|
||||
val newName: String? = null,
|
||||
val index: Int = -1
|
||||
val index: Int = -1,
|
||||
)
|
||||
|
@ -8,5 +8,5 @@ data class Playlists(
|
||||
var name: String? = null,
|
||||
val shortDescription: String? = null,
|
||||
val thumbnail: String? = null,
|
||||
val videos: Long = 0
|
||||
val videos: Long = 0,
|
||||
)
|
||||
|
@ -10,5 +10,5 @@ data class PreviewFrames(
|
||||
val totalCount: Int? = null,
|
||||
val durationPerFrame: Int? = null,
|
||||
val framesPerPageX: Int? = null,
|
||||
val framesPerPageY: Int? = null
|
||||
val framesPerPageY: Int? = null,
|
||||
)
|
||||
|
@ -7,5 +7,5 @@ data class SearchResult(
|
||||
val items: List<ContentItem> = emptyList(),
|
||||
val nextpage: String? = null,
|
||||
val suggestion: String? = null,
|
||||
val corrected: Boolean? = null
|
||||
val corrected: Boolean? = null,
|
||||
)
|
||||
|
@ -12,5 +12,5 @@ data class Segment(
|
||||
val segment: List<Double> = listOf(),
|
||||
val userID: String? = null,
|
||||
val videoDuration: Double? = null,
|
||||
val votes: Int? = null
|
||||
val votes: Int? = null,
|
||||
)
|
||||
|
@ -6,5 +6,5 @@ import kotlinx.serialization.Serializable
|
||||
data class SegmentData(
|
||||
val hash: String? = null,
|
||||
val segments: List<Segment> = listOf(),
|
||||
val videoID: String? = null
|
||||
val videoID: String? = null,
|
||||
)
|
||||
|
@ -19,7 +19,7 @@ data class StreamItem(
|
||||
val uploaderVerified: Boolean? = null,
|
||||
val uploaded: Long? = null,
|
||||
val shortDescription: String? = null,
|
||||
val isShort: Boolean = false
|
||||
val isShort: Boolean = false,
|
||||
) {
|
||||
fun toLocalPlaylistItem(playlistId: String): LocalPlaylistItem {
|
||||
return LocalPlaylistItem(
|
||||
@ -31,7 +31,7 @@ data class StreamItem(
|
||||
uploaderUrl = uploaderUrl,
|
||||
uploaderAvatar = uploaderAvatar,
|
||||
uploadDate = uploadedDate,
|
||||
duration = duration
|
||||
duration = duration,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -3,9 +3,9 @@ package com.github.libretube.api.obj
|
||||
import com.github.libretube.db.obj.DownloadItem
|
||||
import com.github.libretube.enums.FileType
|
||||
import com.github.libretube.helpers.ProxyHelper
|
||||
import java.nio.file.Paths
|
||||
import kotlinx.datetime.LocalDate
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.nio.file.Paths
|
||||
|
||||
@Serializable
|
||||
data class Streams(
|
||||
@ -33,7 +33,7 @@ data class Streams(
|
||||
val proxyUrl: String? = null,
|
||||
val chapters: List<ChapterSegment> = emptyList(),
|
||||
val uploaderSubscriberCount: Long = 0,
|
||||
val previewFrames: List<PreviewFrames> = emptyList()
|
||||
val previewFrames: List<PreviewFrames> = emptyList(),
|
||||
) {
|
||||
fun toDownloadItems(
|
||||
videoId: String,
|
||||
@ -42,7 +42,7 @@ data class Streams(
|
||||
videoQuality: String?,
|
||||
audioFormat: String?,
|
||||
audioQuality: String?,
|
||||
subtitleCode: String?
|
||||
subtitleCode: String?,
|
||||
): List<DownloadItem> {
|
||||
val items = mutableListOf<DownloadItem>()
|
||||
|
||||
@ -58,8 +58,8 @@ data class Streams(
|
||||
path = Paths.get(""),
|
||||
url = stream?.url?.let { ProxyHelper.unwrapIfEnabled(it) },
|
||||
format = videoFormat,
|
||||
quality = videoQuality
|
||||
)
|
||||
quality = videoQuality,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@ -75,8 +75,8 @@ data class Streams(
|
||||
path = Paths.get(""),
|
||||
url = stream?.url?.let { ProxyHelper.unwrapIfEnabled(it) },
|
||||
format = audioFormat,
|
||||
quality = audioQuality
|
||||
)
|
||||
quality = audioQuality,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@ -89,8 +89,8 @@ data class Streams(
|
||||
path = Paths.get(""),
|
||||
url = subtitles.find {
|
||||
it.code == subtitleCode
|
||||
}?.url?.let { ProxyHelper.unwrapIfEnabled(it) }
|
||||
)
|
||||
}?.url?.let { ProxyHelper.unwrapIfEnabled(it) },
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@ -110,7 +110,7 @@ data class Streams(
|
||||
duration = duration,
|
||||
views = views,
|
||||
uploaderVerified = uploaderVerified,
|
||||
shortDescription = description
|
||||
shortDescription = description,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -7,5 +7,5 @@ data class Subscription(
|
||||
val url: String,
|
||||
val name: String,
|
||||
val avatar: String,
|
||||
val verified: Boolean
|
||||
val verified: Boolean,
|
||||
)
|
||||
|
@ -8,5 +8,5 @@ data class Subtitle(
|
||||
val mimeType: String? = null,
|
||||
val name: String? = null,
|
||||
val code: String? = null,
|
||||
val autoGenerated: Boolean? = null
|
||||
val autoGenerated: Boolean? = null,
|
||||
)
|
||||
|
@ -5,5 +5,5 @@ import kotlinx.serialization.Serializable
|
||||
@Serializable
|
||||
data class Token(
|
||||
val token: String? = null,
|
||||
val error: String? = null
|
||||
val error: String? = null,
|
||||
)
|
||||
|
@ -17,7 +17,7 @@ class PictureInPictureParamsCompat private constructor(
|
||||
private val title: CharSequence?,
|
||||
private val subtitle: CharSequence?,
|
||||
private val aspectRatio: Rational?,
|
||||
private val expandedAspectRatio: Rational?
|
||||
private val expandedAspectRatio: Rational?,
|
||||
) {
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
fun toPictureInPictureParams(): PictureInPictureParams {
|
||||
@ -109,7 +109,7 @@ class PictureInPictureParamsCompat private constructor(
|
||||
title,
|
||||
subtitle,
|
||||
aspectRatio,
|
||||
expandedAspectRatio
|
||||
expandedAspectRatio,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -21,8 +21,6 @@ const val FAQ_URL = "https://libre-tube.github.io/#faq"
|
||||
const val MATRIX_URL = "https://matrix.to/#/#LibreTube:matrix.org"
|
||||
const val MASTODON_URL = "https://fosstodon.org/@libretube"
|
||||
const val TELEGRAM_URL = "https://t.me/libretube"
|
||||
const val DISCORD_URL = "https://discord.com/invite/Qc34xCj2GV"
|
||||
const val REDDIT_URL = "https://www.reddit.com/r/Libretube/"
|
||||
|
||||
/**
|
||||
* Share Dialog
|
||||
|
@ -38,6 +38,7 @@ object PreferenceKeys {
|
||||
const val ALTERNATIVE_VIDEOS_LAYOUT = "alternative_videos_layout"
|
||||
const val NEW_VIDEOS_BADGE = "new_videos_badge"
|
||||
const val PLAYLISTS_ORDER = "playlists_order"
|
||||
const val PLAYLIST_SORT_ORDER = "playlist_sort_order"
|
||||
|
||||
/**
|
||||
* Instance
|
||||
@ -97,6 +98,7 @@ object PreferenceKeys {
|
||||
const val AUTO_FULLSCREEN_SHORTS = "auto_fullscreen_shorts"
|
||||
const val PLAY_AUTOMATICALLY = "play_automatically"
|
||||
const val FULLSCREEN_GESTURES = "fullscreen_gestures"
|
||||
const val UNLIMITED_SEARCH_HISTORY = "unlimited_search_history"
|
||||
|
||||
/**
|
||||
* Background mode
|
||||
@ -108,6 +110,7 @@ object PreferenceKeys {
|
||||
*/
|
||||
const val NOTIFICATION_ENABLED = "notification_toggle"
|
||||
const val SHOW_STREAM_THUMBNAILS = "show_stream_thumbnails"
|
||||
const val SHORTS_NOTIFICATIONS = "shorts_notifications"
|
||||
const val CHECKING_FREQUENCY = "checking_frequency"
|
||||
const val REQUIRED_NETWORK = "required_network"
|
||||
const val IGNORED_NOTIFICATION_CHANNELS = "ignored_notification_channels"
|
||||
|
@ -37,15 +37,15 @@ import com.github.libretube.db.obj.WatchPosition
|
||||
LocalPlaylistItem::class,
|
||||
Download::class,
|
||||
DownloadItem::class,
|
||||
SubscriptionGroup::class
|
||||
SubscriptionGroup::class,
|
||||
],
|
||||
version = 11,
|
||||
autoMigrations = [
|
||||
AutoMigration(from = 7, to = 8),
|
||||
AutoMigration(from = 8, to = 9),
|
||||
AutoMigration(from = 9, to = 10),
|
||||
AutoMigration(from = 10, to = 11)
|
||||
]
|
||||
AutoMigration(from = 10, to = 11),
|
||||
],
|
||||
)
|
||||
@TypeConverters(Converters::class)
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
|
@ -2,12 +2,12 @@ package com.github.libretube.db
|
||||
|
||||
import androidx.room.TypeConverter
|
||||
import com.github.libretube.api.JsonHelper
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import kotlinx.datetime.LocalDate
|
||||
import kotlinx.datetime.toLocalDate
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.encodeToString
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
|
||||
object Converters {
|
||||
@TypeConverter
|
||||
|
@ -22,7 +22,7 @@ object DatabaseHelper {
|
||||
streams.uploaderUrl.toID(),
|
||||
streams.uploaderAvatar,
|
||||
streams.thumbnailUrl,
|
||||
streams.duration
|
||||
streams.duration,
|
||||
)
|
||||
Database.watchHistoryDao().insert(watchHistoryItem)
|
||||
val maxHistorySize = PreferenceHelper.getString(PreferenceKeys.WATCH_HISTORY_SIZE, "100")
|
||||
@ -40,10 +40,14 @@ object DatabaseHelper {
|
||||
suspend fun addToSearchHistory(searchHistoryItem: SearchHistoryItem) {
|
||||
Database.searchHistoryDao().insert(searchHistoryItem)
|
||||
|
||||
if (PreferenceHelper.getBoolean(PreferenceKeys.UNLIMITED_SEARCH_HISTORY, false)) return
|
||||
|
||||
// delete the first watch history entry if the limit is reached
|
||||
val searchHistory = Database.searchHistoryDao().getAll()
|
||||
if (searchHistory.size > MAX_SEARCH_HISTORY_SIZE) {
|
||||
val searchHistory = Database.searchHistoryDao().getAll().toMutableList()
|
||||
|
||||
while (searchHistory.size > MAX_SEARCH_HISTORY_SIZE) {
|
||||
Database.searchHistoryDao().delete(searchHistory.first())
|
||||
searchHistory.removeFirst()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,5 +10,5 @@ import kotlinx.serialization.Serializable
|
||||
class CustomInstance(
|
||||
@PrimaryKey var name: String = "",
|
||||
@ColumnInfo var apiUrl: String = "",
|
||||
@ColumnInfo var frontendUrl: String = ""
|
||||
@ColumnInfo var frontendUrl: String = "",
|
||||
)
|
||||
|
@ -2,8 +2,8 @@ package com.github.libretube.db.obj
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import java.nio.file.Path
|
||||
import kotlinx.datetime.LocalDate
|
||||
import java.nio.file.Path
|
||||
|
||||
@Entity(tableName = "download")
|
||||
data class Download(
|
||||
@ -13,5 +13,5 @@ data class Download(
|
||||
val description: String = "",
|
||||
val uploader: String = "",
|
||||
val uploadDate: LocalDate? = null,
|
||||
val thumbnailPath: Path? = null
|
||||
val thumbnailPath: Path? = null,
|
||||
)
|
||||
|
@ -15,9 +15,9 @@ import java.nio.file.Path
|
||||
entity = Download::class,
|
||||
parentColumns = ["videoId"],
|
||||
childColumns = ["videoId"],
|
||||
onDelete = ForeignKey.CASCADE
|
||||
)
|
||||
]
|
||||
onDelete = ForeignKey.CASCADE,
|
||||
),
|
||||
],
|
||||
)
|
||||
data class DownloadItem(
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
@ -29,5 +29,5 @@ data class DownloadItem(
|
||||
var url: String? = null,
|
||||
var format: String? = null,
|
||||
var quality: String? = null,
|
||||
var downloadSize: Long = -1L
|
||||
var downloadSize: Long = -1L,
|
||||
)
|
||||
|
@ -7,7 +7,7 @@ data class DownloadWithItems(
|
||||
@Embedded val download: Download,
|
||||
@Relation(
|
||||
parentColumn = "videoId",
|
||||
entityColumn = "videoId"
|
||||
entityColumn = "videoId",
|
||||
)
|
||||
val downloadItems: List<DownloadItem>
|
||||
val downloadItems: List<DownloadItem>,
|
||||
)
|
||||
|
@ -10,5 +10,5 @@ data class LocalPlaylist(
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
val id: Int = 0,
|
||||
var name: String = "",
|
||||
var thumbnailUrl: String = ""
|
||||
var thumbnailUrl: String = "",
|
||||
)
|
||||
|
@ -19,7 +19,7 @@ data class LocalPlaylistItem(
|
||||
@ColumnInfo val uploaderUrl: String? = null,
|
||||
@ColumnInfo val uploaderAvatar: String? = null,
|
||||
@ColumnInfo val thumbnailUrl: String? = null,
|
||||
@ColumnInfo val duration: Long? = null
|
||||
@ColumnInfo val duration: Long? = null,
|
||||
) {
|
||||
fun toStreamItem(): StreamItem {
|
||||
return StreamItem(
|
||||
@ -31,7 +31,7 @@ data class LocalPlaylistItem(
|
||||
uploaderAvatar = ProxyHelper.rewriteUrl(uploaderAvatar),
|
||||
uploadedDate = uploadDate,
|
||||
uploaded = null,
|
||||
duration = duration
|
||||
duration = duration,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ data class LocalPlaylistWithVideos(
|
||||
@Embedded val playlist: LocalPlaylist = LocalPlaylist(),
|
||||
@Relation(
|
||||
parentColumn = "id",
|
||||
entityColumn = "playlistId"
|
||||
entityColumn = "playlistId",
|
||||
)
|
||||
val videos: List<LocalPlaylistItem> = listOf()
|
||||
val videos: List<LocalPlaylistItem> = listOf(),
|
||||
)
|
||||
|
@ -7,5 +7,5 @@ import kotlinx.serialization.Serializable
|
||||
@Serializable
|
||||
@Entity(tableName = "localSubscription")
|
||||
data class LocalSubscription(
|
||||
@PrimaryKey val channelId: String = ""
|
||||
@PrimaryKey val channelId: String = "",
|
||||
)
|
||||
|
@ -13,5 +13,5 @@ data class PlaylistBookmark(
|
||||
var thumbnailUrl: String? = null,
|
||||
var uploader: String? = null,
|
||||
var uploaderUrl: String? = null,
|
||||
var uploaderAvatar: String? = null
|
||||
var uploaderAvatar: String? = null,
|
||||
)
|
||||
|
@ -7,5 +7,5 @@ import kotlinx.serialization.Serializable
|
||||
@Serializable
|
||||
@Entity(tableName = "searchHistoryItem")
|
||||
data class SearchHistoryItem(
|
||||
@PrimaryKey val query: String = ""
|
||||
@PrimaryKey val query: String = "",
|
||||
)
|
||||
|
@ -8,5 +8,5 @@ import kotlinx.serialization.Serializable
|
||||
@Entity(tableName = "subscriptionGroups")
|
||||
data class SubscriptionGroup(
|
||||
@PrimaryKey var name: String,
|
||||
val channels: MutableList<String>
|
||||
val channels: MutableList<String>,
|
||||
)
|
||||
|
@ -16,5 +16,5 @@ data class WatchHistoryItem(
|
||||
@ColumnInfo val uploaderUrl: String? = null,
|
||||
@ColumnInfo var uploaderAvatar: String? = null,
|
||||
@ColumnInfo var thumbnailUrl: String? = null,
|
||||
@ColumnInfo val duration: Long? = null
|
||||
@ColumnInfo val duration: Long? = null,
|
||||
)
|
||||
|
@ -9,5 +9,5 @@ import kotlinx.serialization.Serializable
|
||||
@Entity(tableName = "watchPosition")
|
||||
data class WatchPosition(
|
||||
@PrimaryKey val videoId: String = "",
|
||||
@ColumnInfo val position: Long = 0L
|
||||
@ColumnInfo val position: Long = 0L,
|
||||
)
|
||||
|
@ -2,5 +2,5 @@ package com.github.libretube.enums
|
||||
|
||||
enum class AudioQuality {
|
||||
BEST,
|
||||
WORST
|
||||
WORST,
|
||||
}
|
||||
|
@ -3,5 +3,5 @@ package com.github.libretube.enums
|
||||
enum class FileType {
|
||||
AUDIO,
|
||||
VIDEO,
|
||||
SUBTITLE
|
||||
SUBTITLE,
|
||||
}
|
||||
|
@ -7,7 +7,8 @@ enum class PlayerEvent(val value: Int) {
|
||||
Rewind(3),
|
||||
Next(5),
|
||||
Prev(6),
|
||||
Background(7);
|
||||
Background(7),
|
||||
;
|
||||
|
||||
companion object {
|
||||
fun fromInt(value: Int) = PlayerEvent.values().first { it.value == value }
|
||||
|
@ -14,5 +14,5 @@ enum class PlaylistType {
|
||||
/**
|
||||
* YouTube playlist
|
||||
*/
|
||||
PUBLIC
|
||||
PUBLIC,
|
||||
}
|
||||
|
@ -3,5 +3,5 @@ package com.github.libretube.enums
|
||||
enum class ShareObjectType {
|
||||
VIDEO,
|
||||
PLAYLIST,
|
||||
CHANNEL
|
||||
CHANNEL,
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
package com.github.libretube.extensions
|
||||
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
|
||||
suspend fun URL.getContentLength(def: Long = -1): Long {
|
||||
try {
|
||||
|
@ -5,7 +5,7 @@ import kotlin.math.roundToInt
|
||||
|
||||
fun Float.round(decimalPlaces: Int): Float {
|
||||
return (this * 10.0.pow(decimalPlaces.toDouble())).roundToInt() / 10.0.pow(
|
||||
decimalPlaces.toDouble()
|
||||
decimalPlaces.toDouble(),
|
||||
)
|
||||
.toFloat()
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ fun Context.toastFromMainThread(text: String) {
|
||||
Toast.makeText(
|
||||
this,
|
||||
text,
|
||||
Toast.LENGTH_SHORT
|
||||
Toast.LENGTH_SHORT,
|
||||
).show()
|
||||
}
|
||||
}
|
||||
|
@ -3,5 +3,5 @@ package com.github.libretube.extensions
|
||||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector
|
||||
|
||||
inline fun DefaultTrackSelector.updateParameters(
|
||||
actions: DefaultTrackSelector.Parameters.Builder.() -> Unit
|
||||
actions: DefaultTrackSelector.Parameters.Builder.() -> Unit,
|
||||
) = setParameters(buildUponParameters().apply(actions))
|
||||
|
@ -27,7 +27,7 @@ object BackgroundHelper {
|
||||
playlistId: String? = null,
|
||||
channelId: String? = null,
|
||||
keepQueue: Boolean? = null,
|
||||
keepVideoPlayerAlive: Boolean = false
|
||||
keepVideoPlayerAlive: Boolean = false,
|
||||
) {
|
||||
// close the previous video player if open
|
||||
if (!keepVideoPlayerAlive) {
|
||||
|
@ -56,7 +56,7 @@ class BrightnessHelper(private val activity: Activity) {
|
||||
value: Float,
|
||||
maxValue: Float,
|
||||
minValue: Float = 0.0f,
|
||||
shouldSave: Boolean = false
|
||||
shouldSave: Boolean = false,
|
||||
) {
|
||||
brightness = value.normalize(minValue, maxValue, minBrightness, maxBrightness)
|
||||
if (shouldSave) savedBrightness = brightness
|
||||
@ -69,7 +69,7 @@ class BrightnessHelper(private val activity: Activity) {
|
||||
fun getBrightnessWithScale(
|
||||
maxValue: Float,
|
||||
minValue: Float = 0.0f,
|
||||
saved: Boolean = false
|
||||
saved: Boolean = false,
|
||||
): Float {
|
||||
return if (saved) {
|
||||
savedBrightness.normalize(minBrightness, maxBrightness, minValue, maxValue)
|
||||
|
@ -2,14 +2,14 @@ package com.github.libretube.helpers
|
||||
|
||||
import com.github.libretube.api.obj.PipedStream
|
||||
import com.github.libretube.api.obj.Streams
|
||||
import org.w3c.dom.Document
|
||||
import org.w3c.dom.Element
|
||||
import java.io.StringWriter
|
||||
import javax.xml.parsers.DocumentBuilder
|
||||
import javax.xml.parsers.DocumentBuilderFactory
|
||||
import javax.xml.transform.TransformerFactory
|
||||
import javax.xml.transform.dom.DOMSource
|
||||
import javax.xml.transform.stream.StreamResult
|
||||
import org.w3c.dom.Document
|
||||
import org.w3c.dom.Element
|
||||
|
||||
// Based off of https://github.com/TeamPiped/Piped/blob/master/src/utils/DashUtils.js
|
||||
|
||||
@ -21,10 +21,10 @@ object DashHelper {
|
||||
private data class AdapSetInfo(
|
||||
val mimeType: String,
|
||||
val audioTrackId: String? = null,
|
||||
val formats: MutableList<PipedStream> = mutableListOf()
|
||||
val formats: MutableList<PipedStream> = mutableListOf(),
|
||||
)
|
||||
|
||||
fun createManifest(streams: Streams): String {
|
||||
fun createManifest(streams: Streams, supportsHdr: Boolean): String {
|
||||
val builder: DocumentBuilder = builderFactory.newDocumentBuilder()
|
||||
|
||||
val doc = builder.newDocument()
|
||||
@ -40,17 +40,18 @@ object DashHelper {
|
||||
val adapSetInfos = ArrayList<AdapSetInfo>()
|
||||
|
||||
val enabledVideoCodecs = PlayerHelper.enabledVideoCodecs
|
||||
for (stream in streams.videoStreams
|
||||
for (
|
||||
stream in streams.videoStreams
|
||||
// used to avoid including LBRY HLS inside the streams in the manifest
|
||||
.filter { !it.format.orEmpty().contains("HLS") }
|
||||
// filter the codecs according to the user's preferences
|
||||
.filter {
|
||||
if (enabledVideoCodecs != "all") {
|
||||
it.codec?.lowercase()?.startsWith(enabledVideoCodecs) ?: true
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}) {
|
||||
enabledVideoCodecs == "all" || it.codec.orEmpty().lowercase().startsWith(
|
||||
enabledVideoCodecs,
|
||||
)
|
||||
}
|
||||
.filter { supportsHdr || !it.quality.orEmpty().uppercase().contains("HDR") }
|
||||
) {
|
||||
// ignore dual format streams
|
||||
if (!stream.videoOnly!!) {
|
||||
continue
|
||||
@ -70,8 +71,8 @@ object DashHelper {
|
||||
AdapSetInfo(
|
||||
stream.mimeType!!,
|
||||
null,
|
||||
mutableListOf(stream)
|
||||
)
|
||||
mutableListOf(stream),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@ -88,8 +89,8 @@ object DashHelper {
|
||||
AdapSetInfo(
|
||||
stream.mimeType!!,
|
||||
stream.audioTrackId,
|
||||
mutableListOf(stream)
|
||||
)
|
||||
mutableListOf(stream),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@ -144,7 +145,7 @@ object DashHelper {
|
||||
val audioChannelConfiguration = doc.createElement("AudioChannelConfiguration")
|
||||
audioChannelConfiguration.setAttribute(
|
||||
"schemeIdUri",
|
||||
"urn:mpeg:dash:23003:3:audio_channel_configuration:2011"
|
||||
"urn:mpeg:dash:23003:3:audio_channel_configuration:2011",
|
||||
)
|
||||
audioChannelConfiguration.setAttribute("value", "2")
|
||||
|
||||
|
@ -0,0 +1,22 @@
|
||||
package com.github.libretube.helpers
|
||||
|
||||
import android.content.Context
|
||||
import android.hardware.display.DisplayManager
|
||||
import android.os.Build
|
||||
import android.view.Display
|
||||
|
||||
object DisplayHelper {
|
||||
/**
|
||||
* Detect whether the device supports HDR as the ExoPlayer doesn't handle it properly
|
||||
* Returns false on and below SDK 24
|
||||
*/
|
||||
fun supportsHdr(context: Context): Boolean {
|
||||
val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
|
||||
val display = displayManager.getDisplay(Display.DEFAULT_DISPLAY)
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
display.hdrCapabilities.supportedHdrTypes.isNotEmpty()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
@ -44,7 +44,7 @@ object DownloadHelper {
|
||||
fun getMaxConcurrentDownloads(): Int {
|
||||
return PreferenceHelper.getString(
|
||||
PreferenceKeys.MAX_CONCURRENT_DOWNLOADS,
|
||||
"6"
|
||||
"6",
|
||||
).toFloat().toInt()
|
||||
}
|
||||
|
||||
@ -56,7 +56,7 @@ object DownloadHelper {
|
||||
videoQuality: String? = null,
|
||||
audioFormat: String? = null,
|
||||
audioQuality: String? = null,
|
||||
subtitleCode: String? = null
|
||||
subtitleCode: String? = null,
|
||||
) {
|
||||
val intent = Intent(context, DownloadService::class.java)
|
||||
|
||||
|
@ -28,7 +28,7 @@ object ImageHelper {
|
||||
fun initializeImageLoader(context: Context) {
|
||||
val maxImageCacheSize = PreferenceHelper.getString(
|
||||
PreferenceKeys.MAX_IMAGE_CACHE,
|
||||
""
|
||||
"",
|
||||
)
|
||||
|
||||
imageLoader = ImageLoader.Builder(context)
|
||||
@ -42,7 +42,7 @@ object ImageHelper {
|
||||
DiskCache.Builder()
|
||||
.directory(context.cacheDir.resolve("coil"))
|
||||
.maxSizeBytes(maxImageCacheSize.toInt() * 1024 * 1024L)
|
||||
.build()
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -109,7 +109,7 @@ object ImageHelper {
|
||||
(bitmap.width - newSize) / 2,
|
||||
(bitmap.height - newSize) / 2,
|
||||
newSize,
|
||||
newSize
|
||||
newSize,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -15,10 +15,10 @@ import com.github.libretube.obj.ImportPlaylist
|
||||
import com.github.libretube.obj.ImportPlaylistFile
|
||||
import com.github.libretube.obj.NewPipeSubscription
|
||||
import com.github.libretube.obj.NewPipeSubscriptions
|
||||
import kotlin.streams.toList
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.json.decodeFromStream
|
||||
import kotlinx.serialization.json.encodeToStream
|
||||
import kotlin.streams.toList
|
||||
|
||||
object ImportHelper {
|
||||
/**
|
||||
@ -44,6 +44,7 @@ object ImportHelper {
|
||||
/**
|
||||
* Get a list of channel IDs from a file [Uri]
|
||||
*/
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
private fun getChannelsFromUri(activity: Activity, uri: Uri): List<String> {
|
||||
return when (val fileType = activity.contentResolver.getType(uri)) {
|
||||
"application/json", "application/*", "application/octet-stream" -> {
|
||||
@ -72,6 +73,7 @@ object ImportHelper {
|
||||
/**
|
||||
* Write the text to the document
|
||||
*/
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
suspend fun exportSubscriptions(activity: Activity, uri: Uri) {
|
||||
val token = PreferenceHelper.getToken()
|
||||
val subs = if (token.isNotEmpty()) {
|
||||
@ -105,10 +107,14 @@ object ImportHelper {
|
||||
activity.contentResolver.openInputStream(uri)?.use {
|
||||
val lines = it.bufferedReader().use { reader -> reader.lines().toList() }
|
||||
playlist.name = lines[1].split(",").reversed()[2]
|
||||
val splitIndex = lines.indexOfFirst { line -> line.isBlank() }
|
||||
var splitIndex = lines.indexOfFirst { line -> line.isBlank() }
|
||||
// seek until playlist items table
|
||||
while (lines.getOrNull(splitIndex + 1).orEmpty().isBlank()) {
|
||||
splitIndex++
|
||||
}
|
||||
lines.subList(splitIndex + 2, lines.size).forEach { line ->
|
||||
line.split(",").firstOrNull()?.let { videoId ->
|
||||
if (videoId.isNotBlank()) playlist.videos = playlist.videos + videoId
|
||||
if (videoId.isNotBlank()) playlist.videos += videoId.trim()
|
||||
}
|
||||
}
|
||||
importPlaylists.add(playlist)
|
||||
@ -145,6 +151,7 @@ object ImportHelper {
|
||||
/**
|
||||
* Export Playlists
|
||||
*/
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
suspend fun exportPlaylists(activity: Activity, uri: Uri) {
|
||||
val playlists = PlaylistsHelper.exportPlaylists()
|
||||
val playlistFile = ImportPlaylistFile("Piped", 1, playlists)
|
||||
|
@ -21,7 +21,7 @@ object IntentHelper {
|
||||
context.packageManager
|
||||
.queryIntentActivities(
|
||||
intent,
|
||||
PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_ALL.toLong())
|
||||
PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_ALL.toLong()),
|
||||
)
|
||||
} else {
|
||||
context.packageManager
|
||||
|
@ -20,7 +20,7 @@ object LocaleHelper {
|
||||
val languageParts = languageName.split("-")
|
||||
Locale(
|
||||
languageParts[0],
|
||||
languageParts[1].replace("r", "")
|
||||
languageParts[1].replace("r", ""),
|
||||
)
|
||||
}
|
||||
else -> Locale(languageName)
|
||||
|
@ -23,7 +23,7 @@ object NavBarHelper {
|
||||
val prefItems = try {
|
||||
PreferenceHelper.getString(
|
||||
PreferenceKeys.NAVBAR_ITEMS,
|
||||
""
|
||||
"",
|
||||
).split(SEPARATOR)
|
||||
} catch (e: Exception) {
|
||||
Log.e("fail to parse nav items", e.toString())
|
||||
@ -38,7 +38,7 @@ object NavBarHelper {
|
||||
navBarItems.add(
|
||||
p.menu[it.replace("-", "").toInt()].apply {
|
||||
this.isVisible = !it.contains("-")
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
return navBarItems
|
||||
@ -65,7 +65,7 @@ object NavBarHelper {
|
||||
}
|
||||
PreferenceHelper.putString(
|
||||
PreferenceKeys.NAVBAR_ITEMS,
|
||||
prefString.joinToString(SEPARATOR)
|
||||
prefString.joinToString(SEPARATOR),
|
||||
)
|
||||
}
|
||||
|
||||
@ -90,7 +90,7 @@ object NavBarHelper {
|
||||
// remove the old items
|
||||
navBarItems.forEach {
|
||||
menuItems.add(
|
||||
bottomNav.menu.findItem(it.itemId)
|
||||
bottomNav.menu.findItem(it.itemId),
|
||||
)
|
||||
bottomNav.menu.removeItem(it.itemId)
|
||||
}
|
||||
@ -103,7 +103,7 @@ object NavBarHelper {
|
||||
menuItem.groupId,
|
||||
menuItem.itemId,
|
||||
Menu.NONE,
|
||||
menuItem.title
|
||||
menuItem.title,
|
||||
).icon = menuItem.icon
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ object NavigationHelper {
|
||||
|
||||
fun navigateChannel(
|
||||
context: Context,
|
||||
channelId: String?
|
||||
channelId: String?,
|
||||
) {
|
||||
if (channelId == null) return
|
||||
|
||||
@ -64,7 +64,7 @@ object NavigationHelper {
|
||||
channelId: String? = null,
|
||||
keepQueue: Boolean = false,
|
||||
timeStamp: Long? = null,
|
||||
forceVideo: Boolean = false
|
||||
forceVideo: Boolean = false,
|
||||
) {
|
||||
if (videoId == null) return
|
||||
|
||||
@ -76,7 +76,7 @@ object NavigationHelper {
|
||||
timeStamp,
|
||||
playlistId,
|
||||
channelId,
|
||||
keepQueue
|
||||
keepQueue,
|
||||
)
|
||||
handler.postDelayed(500) {
|
||||
startAudioPlayer(context)
|
||||
@ -89,7 +89,7 @@ object NavigationHelper {
|
||||
IntentData.playlistId to playlistId,
|
||||
IntentData.channelId to channelId,
|
||||
IntentData.keepQueue to keepQueue,
|
||||
IntentData.timeStamp to timeStamp
|
||||
IntentData.timeStamp to timeStamp,
|
||||
)
|
||||
|
||||
val activity = unwrapActivity(context)
|
||||
@ -101,14 +101,14 @@ object NavigationHelper {
|
||||
fun navigatePlaylist(
|
||||
context: Context,
|
||||
playlistId: String?,
|
||||
playlistType: PlaylistType
|
||||
playlistType: PlaylistType,
|
||||
) {
|
||||
if (playlistId == null) return
|
||||
|
||||
val activity = unwrapActivity(context)
|
||||
val bundle = bundleOf(
|
||||
IntentData.playlistId to playlistId,
|
||||
IntentData.playlistType to playlistType
|
||||
IntentData.playlistType to playlistType,
|
||||
)
|
||||
activity.navController.navigate(R.id.playlistFragment, bundle)
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ object NetworkHelper {
|
||||
/**
|
||||
* Detect whether network is available
|
||||
*/
|
||||
@Suppress("DEPRECATION")
|
||||
fun isNetworkAvailable(context: Context): Boolean {
|
||||
// In case we are using a VPN, we return true since we might be using reverse tethering
|
||||
val connectivityManager = context.getSystemService<ConnectivityManager>() ?: return false
|
||||
@ -36,6 +37,7 @@ object NetworkHelper {
|
||||
* @param context Context of the application
|
||||
* @return whether the network is metered or not
|
||||
*/
|
||||
@Suppress("DEPRECATION")
|
||||
fun isNetworkMetered(context: Context): Boolean {
|
||||
val connectivityManager = context.getSystemService<ConnectivityManager>()!!
|
||||
val activeNetworkInfo = connectivityManager.activeNetworkInfo
|
||||
|
@ -17,18 +17,18 @@ object NotificationHelper {
|
||||
*/
|
||||
fun enqueueWork(
|
||||
context: Context,
|
||||
existingPeriodicWorkPolicy: ExistingPeriodicWorkPolicy
|
||||
existingPeriodicWorkPolicy: ExistingPeriodicWorkPolicy,
|
||||
) {
|
||||
// get the notification preferences
|
||||
PreferenceHelper.initialize(context)
|
||||
val notificationsEnabled = PreferenceHelper.getBoolean(
|
||||
PreferenceKeys.NOTIFICATION_ENABLED,
|
||||
true
|
||||
true,
|
||||
)
|
||||
|
||||
val checkingFrequency = PreferenceHelper.getString(
|
||||
PreferenceKeys.CHECKING_FREQUENCY,
|
||||
"60"
|
||||
"60",
|
||||
).toLong()
|
||||
|
||||
// schedule the work manager request if logged in and notifications enabled
|
||||
@ -58,7 +58,7 @@ object NotificationHelper {
|
||||
// create the worker
|
||||
val notificationWorker = PeriodicWorkRequestBuilder<NotificationWorker>(
|
||||
checkingFrequency,
|
||||
TimeUnit.MINUTES
|
||||
TimeUnit.MINUTES,
|
||||
)
|
||||
.setConstraints(constraints)
|
||||
.build()
|
||||
@ -68,7 +68,7 @@ object NotificationHelper {
|
||||
.enqueueUniquePeriodicWork(
|
||||
NOTIFICATION_WORK_NAME,
|
||||
existingPeriodicWorkPolicy,
|
||||
notificationWorker
|
||||
notificationWorker,
|
||||
)
|
||||
|
||||
// for testing the notifications by the work manager
|
||||
@ -79,6 +79,6 @@ object NotificationHelper {
|
||||
.setConstraints(constraints)
|
||||
.build()
|
||||
)
|
||||
*/
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ object PlayerHelper {
|
||||
|
||||
return getBitRate(
|
||||
filteredAudios,
|
||||
if (audioQuality == "best") AudioQuality.BEST else AudioQuality.WORST
|
||||
if (audioQuality == "best") AudioQuality.BEST else AudioQuality.WORST,
|
||||
)
|
||||
}
|
||||
|
||||
@ -93,56 +93,56 @@ object PlayerHelper {
|
||||
val categories: ArrayList<String> = arrayListOf()
|
||||
if (PreferenceHelper.getBoolean(
|
||||
"intro_category_key",
|
||||
false
|
||||
false,
|
||||
)
|
||||
) {
|
||||
categories.add("intro")
|
||||
}
|
||||
if (PreferenceHelper.getBoolean(
|
||||
"selfpromo_category_key",
|
||||
false
|
||||
false,
|
||||
)
|
||||
) {
|
||||
categories.add("selfpromo")
|
||||
}
|
||||
if (PreferenceHelper.getBoolean(
|
||||
"interaction_category_key",
|
||||
false
|
||||
false,
|
||||
)
|
||||
) {
|
||||
categories.add("interaction")
|
||||
}
|
||||
if (PreferenceHelper.getBoolean(
|
||||
"sponsors_category_key",
|
||||
true
|
||||
true,
|
||||
)
|
||||
) {
|
||||
categories.add("sponsor")
|
||||
}
|
||||
if (PreferenceHelper.getBoolean(
|
||||
"outro_category_key",
|
||||
false
|
||||
false,
|
||||
)
|
||||
) {
|
||||
categories.add("outro")
|
||||
}
|
||||
if (PreferenceHelper.getBoolean(
|
||||
"filler_category_key",
|
||||
false
|
||||
false,
|
||||
)
|
||||
) {
|
||||
categories.add("filler")
|
||||
}
|
||||
if (PreferenceHelper.getBoolean(
|
||||
"music_offtopic_category_key",
|
||||
false
|
||||
false,
|
||||
)
|
||||
) {
|
||||
categories.add("music_offtopic")
|
||||
}
|
||||
if (PreferenceHelper.getBoolean(
|
||||
"preview_category_key",
|
||||
false
|
||||
false,
|
||||
)
|
||||
) {
|
||||
categories.add("preview")
|
||||
@ -153,7 +153,7 @@ object PlayerHelper {
|
||||
fun getOrientation(videoWidth: Int, videoHeight: Int): Int {
|
||||
val fullscreenOrientationPref = PreferenceHelper.getString(
|
||||
PreferenceKeys.FULLSCREEN_ORIENTATION,
|
||||
"ratio"
|
||||
"ratio",
|
||||
)
|
||||
|
||||
return when (fullscreenOrientationPref) {
|
||||
@ -176,25 +176,25 @@ object PlayerHelper {
|
||||
val autoRotationEnabled: Boolean
|
||||
get() = PreferenceHelper.getBoolean(
|
||||
PreferenceKeys.AUTO_FULLSCREEN,
|
||||
false
|
||||
false,
|
||||
)
|
||||
|
||||
val relatedStreamsEnabled: Boolean
|
||||
get() = PreferenceHelper.getBoolean(
|
||||
PreferenceKeys.RELATED_STREAMS,
|
||||
true
|
||||
true,
|
||||
)
|
||||
|
||||
val pausePlayerOnScreenOffEnabled: Boolean
|
||||
get() = PreferenceHelper.getBoolean(
|
||||
PreferenceKeys.PAUSE_ON_SCREEN_OFF,
|
||||
false
|
||||
false,
|
||||
)
|
||||
|
||||
private val watchPositionsPref: String
|
||||
get() = PreferenceHelper.getString(
|
||||
PreferenceKeys.WATCH_POSITIONS,
|
||||
"always"
|
||||
"always",
|
||||
)
|
||||
|
||||
val watchPositionsVideo: Boolean
|
||||
@ -206,38 +206,38 @@ object PlayerHelper {
|
||||
val watchHistoryEnabled: Boolean
|
||||
get() = PreferenceHelper.getBoolean(
|
||||
PreferenceKeys.WATCH_HISTORY_TOGGLE,
|
||||
true
|
||||
true,
|
||||
)
|
||||
|
||||
val useSystemCaptionStyle: Boolean
|
||||
get() = PreferenceHelper.getBoolean(
|
||||
PreferenceKeys.SYSTEM_CAPTION_STYLE,
|
||||
true
|
||||
true,
|
||||
)
|
||||
|
||||
private val bufferingGoal: Int
|
||||
get() = PreferenceHelper.getString(
|
||||
PreferenceKeys.BUFFERING_GOAL,
|
||||
"50"
|
||||
"50",
|
||||
).toInt() * 1000
|
||||
|
||||
val sponsorBlockEnabled: Boolean
|
||||
get() = PreferenceHelper.getBoolean(
|
||||
"sb_enabled_key",
|
||||
true
|
||||
true,
|
||||
)
|
||||
|
||||
private val sponsorBlockNotifications: Boolean
|
||||
get() = PreferenceHelper.getBoolean(
|
||||
"sb_notifications_key",
|
||||
true
|
||||
true,
|
||||
)
|
||||
|
||||
val defaultSubtitleCode: String?
|
||||
get() {
|
||||
val code = PreferenceHelper.getString(
|
||||
PreferenceKeys.DEFAULT_SUBTITLE,
|
||||
""
|
||||
"",
|
||||
)
|
||||
|
||||
if (code == "") return null
|
||||
@ -251,37 +251,37 @@ object PlayerHelper {
|
||||
val skipButtonsEnabled: Boolean
|
||||
get() = PreferenceHelper.getBoolean(
|
||||
PreferenceKeys.SKIP_BUTTONS,
|
||||
false
|
||||
false,
|
||||
)
|
||||
|
||||
val pipEnabled: Boolean
|
||||
get() = PreferenceHelper.getBoolean(
|
||||
PreferenceKeys.PICTURE_IN_PICTURE,
|
||||
true
|
||||
true,
|
||||
)
|
||||
|
||||
val skipSegmentsManually: Boolean
|
||||
get() = PreferenceHelper.getBoolean(
|
||||
PreferenceKeys.SB_SKIP_MANUALLY,
|
||||
false
|
||||
false,
|
||||
)
|
||||
|
||||
val autoPlayEnabled: Boolean
|
||||
get() = PreferenceHelper.getBoolean(
|
||||
PreferenceKeys.AUTO_PLAY,
|
||||
true
|
||||
true,
|
||||
)
|
||||
|
||||
val autoPlayCountdown: Boolean
|
||||
get() = PreferenceHelper.getBoolean(
|
||||
PreferenceKeys.AUTOPLAY_COUNTDOWN,
|
||||
false
|
||||
false,
|
||||
)
|
||||
|
||||
val seekIncrement: Long
|
||||
get() = PreferenceHelper.getString(
|
||||
PreferenceKeys.SEEK_INCREMENT,
|
||||
"10.0"
|
||||
"10.0",
|
||||
).toFloat()
|
||||
.roundToInt()
|
||||
.toLong() * 1000
|
||||
@ -289,7 +289,7 @@ object PlayerHelper {
|
||||
private val playbackSpeed: Float
|
||||
get() = PreferenceHelper.getString(
|
||||
PreferenceKeys.PLAYBACK_SPEED,
|
||||
"1"
|
||||
"1",
|
||||
).replace("F", "").toFloat()
|
||||
|
||||
private val backgroundSpeed: Float
|
||||
@ -301,73 +301,73 @@ object PlayerHelper {
|
||||
val resizeModePref: String
|
||||
get() = PreferenceHelper.getString(
|
||||
PreferenceKeys.PLAYER_RESIZE_MODE,
|
||||
"fit"
|
||||
"fit",
|
||||
)
|
||||
|
||||
val alternativeVideoLayout: Boolean
|
||||
get() = PreferenceHelper.getBoolean(
|
||||
PreferenceKeys.ALTERNATIVE_PLAYER_LAYOUT,
|
||||
false
|
||||
false,
|
||||
)
|
||||
|
||||
val autoInsertRelatedVideos: Boolean
|
||||
get() = PreferenceHelper.getBoolean(
|
||||
PreferenceKeys.QUEUE_AUTO_INSERT_RELATED,
|
||||
true
|
||||
true,
|
||||
)
|
||||
|
||||
val swipeGestureEnabled: Boolean
|
||||
get() = PreferenceHelper.getBoolean(
|
||||
PreferenceKeys.PLAYER_SWIPE_CONTROLS,
|
||||
true
|
||||
true,
|
||||
)
|
||||
|
||||
val fullscreenGesturesEnabled: Boolean
|
||||
get() = PreferenceHelper.getBoolean(
|
||||
PreferenceKeys.FULLSCREEN_GESTURES,
|
||||
false
|
||||
false,
|
||||
)
|
||||
|
||||
val pinchGestureEnabled: Boolean
|
||||
get() = PreferenceHelper.getBoolean(
|
||||
PreferenceKeys.PLAYER_PINCH_CONTROL,
|
||||
true
|
||||
true,
|
||||
)
|
||||
|
||||
val captionsTextSize: Float
|
||||
get() = PreferenceHelper.getString(
|
||||
PreferenceKeys.CAPTIONS_SIZE,
|
||||
"18"
|
||||
"18",
|
||||
).toFloat()
|
||||
|
||||
val doubleTapToSeek: Boolean
|
||||
get() = PreferenceHelper.getBoolean(
|
||||
PreferenceKeys.DOUBLE_TAP_TO_SEEK,
|
||||
true
|
||||
true,
|
||||
)
|
||||
|
||||
val pauseOnQuit: Boolean
|
||||
get() = PreferenceHelper.getBoolean(
|
||||
PreferenceKeys.PAUSE_ON_QUIT,
|
||||
false
|
||||
false,
|
||||
)
|
||||
|
||||
private val alternativePiPControls: Boolean
|
||||
get() = PreferenceHelper.getBoolean(
|
||||
PreferenceKeys.ALTERNATIVE_PIP_CONTROLS,
|
||||
false
|
||||
false,
|
||||
)
|
||||
|
||||
private val skipSilence: Boolean
|
||||
get() = PreferenceHelper.getBoolean(
|
||||
PreferenceKeys.SKIP_SILENCE,
|
||||
false
|
||||
false,
|
||||
)
|
||||
|
||||
val enabledVideoCodecs: String
|
||||
get() = PreferenceHelper.getString(
|
||||
PreferenceKeys.ENABLED_VIDEO_CODECS,
|
||||
"all"
|
||||
"all",
|
||||
)
|
||||
|
||||
fun getDefaultResolution(context: Context): String {
|
||||
@ -392,14 +392,14 @@ object PlayerHelper {
|
||||
activity: Activity,
|
||||
id: Int,
|
||||
@StringRes title: Int,
|
||||
event: PlayerEvent
|
||||
event: PlayerEvent,
|
||||
): RemoteActionCompat {
|
||||
val text = activity.getString(title)
|
||||
return RemoteActionCompat(
|
||||
IconCompat.createWithResource(activity, id),
|
||||
text,
|
||||
text,
|
||||
getPendingIntent(activity, event.value)
|
||||
getPendingIntent(activity, event.value),
|
||||
)
|
||||
}
|
||||
|
||||
@ -411,35 +411,35 @@ object PlayerHelper {
|
||||
activity,
|
||||
R.drawable.ic_headphones,
|
||||
R.string.background_mode,
|
||||
PlayerEvent.Background
|
||||
PlayerEvent.Background,
|
||||
)
|
||||
|
||||
val rewindAction = getRemoteAction(
|
||||
activity,
|
||||
R.drawable.ic_rewind,
|
||||
R.string.rewind,
|
||||
PlayerEvent.Rewind
|
||||
PlayerEvent.Rewind,
|
||||
)
|
||||
|
||||
val playPauseAction = getRemoteAction(
|
||||
activity,
|
||||
if (isPlaying) R.drawable.ic_pause else R.drawable.ic_play,
|
||||
R.string.pause,
|
||||
if (isPlaying) PlayerEvent.Pause else PlayerEvent.Play
|
||||
if (isPlaying) PlayerEvent.Pause else PlayerEvent.Play,
|
||||
)
|
||||
|
||||
val skipNextAction = getRemoteAction(
|
||||
activity,
|
||||
R.drawable.ic_next,
|
||||
R.string.play_next,
|
||||
PlayerEvent.Next
|
||||
PlayerEvent.Next,
|
||||
)
|
||||
|
||||
val forwardAction = getRemoteAction(
|
||||
activity,
|
||||
R.drawable.ic_forward,
|
||||
R.string.forward,
|
||||
PlayerEvent.Forward
|
||||
PlayerEvent.Forward,
|
||||
)
|
||||
return if (alternativePiPControls) {
|
||||
listOf(audioModeAction, playPauseAction, skipNextAction)
|
||||
@ -469,7 +469,7 @@ object PlayerHelper {
|
||||
1000 * 10, // exo default is 50s
|
||||
bufferingGoal,
|
||||
DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS,
|
||||
DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS
|
||||
DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS,
|
||||
)
|
||||
.build()
|
||||
}
|
||||
@ -494,7 +494,7 @@ object PlayerHelper {
|
||||
fun ExoPlayer.checkForSegments(
|
||||
context: Context,
|
||||
segments: List<Segment>,
|
||||
skipManually: Boolean = false
|
||||
skipManually: Boolean = false,
|
||||
): Long? {
|
||||
for (segment in segments) {
|
||||
val segmentStart = (segment.segment[0] * 1000f).toLong()
|
||||
|
@ -135,12 +135,12 @@ object PreferenceHelper {
|
||||
ignorableChannels.remove(channelId)
|
||||
} else {
|
||||
ignorableChannels.add(
|
||||
channelId
|
||||
channelId,
|
||||
)
|
||||
}
|
||||
editor.putString(
|
||||
PreferenceKeys.IGNORED_NOTIFICATION_CHANNELS,
|
||||
ignorableChannels.joinToString(",")
|
||||
ignorableChannels.joinToString(","),
|
||||
).apply()
|
||||
}
|
||||
|
||||
|
@ -34,7 +34,7 @@ object ProxyHelper {
|
||||
fun unwrapIfEnabled(url: String): String {
|
||||
if (!PreferenceHelper.getBoolean(
|
||||
PreferenceKeys.DISABLE_VIDEO_IMAGE_PROXY,
|
||||
false
|
||||
false,
|
||||
)
|
||||
) {
|
||||
return url
|
||||
|
@ -15,7 +15,7 @@ object ShortcutHelper {
|
||||
AppShortcut("home", R.string.startpage, R.drawable.ic_home),
|
||||
AppShortcut("trends", R.string.trends, R.drawable.ic_trending),
|
||||
AppShortcut("subscriptions", R.string.subscriptions, R.drawable.ic_subscriptions),
|
||||
AppShortcut("library", R.string.library, R.drawable.ic_library)
|
||||
AppShortcut("library", R.string.library, R.drawable.ic_library),
|
||||
)
|
||||
|
||||
private fun createShortcut(context: Context, appShortcut: AppShortcut): ShortcutInfoCompat {
|
||||
@ -26,7 +26,7 @@ object ShortcutHelper {
|
||||
.setIcon(IconCompat.createWithResource(context, appShortcut.drawable))
|
||||
.setIntent(
|
||||
Intent(Intent.ACTION_VIEW, null, context, MainActivity::class.java)
|
||||
.putExtra(IntentData.fragmentToOpen, appShortcut.action)
|
||||
.putExtra(IntentData.fragmentToOpen, appShortcut.action),
|
||||
)
|
||||
.build()
|
||||
}
|
||||
|
@ -33,12 +33,12 @@ object ThemeHelper {
|
||||
* Update the accent color of the app
|
||||
*/
|
||||
private fun updateAccentColor(
|
||||
activity: AppCompatActivity
|
||||
activity: AppCompatActivity,
|
||||
) {
|
||||
val theme = when (
|
||||
PreferenceHelper.getString(
|
||||
PreferenceKeys.ACCENT_COLOR,
|
||||
"purple"
|
||||
"purple",
|
||||
)
|
||||
) {
|
||||
// set the accent color, use the pure black/white theme if enabled
|
||||
@ -62,7 +62,7 @@ object ThemeHelper {
|
||||
if (
|
||||
PreferenceHelper.getString(
|
||||
PreferenceKeys.ACCENT_COLOR,
|
||||
"purple"
|
||||
"purple",
|
||||
) == "my"
|
||||
) {
|
||||
DynamicColors.applyToActivityIfAvailable(activity)
|
||||
@ -75,7 +75,7 @@ object ThemeHelper {
|
||||
private fun applyPureThemeIfEnabled(activity: Activity) {
|
||||
val pureThemeEnabled = PreferenceHelper.getBoolean(
|
||||
PreferenceKeys.PURE_THEME,
|
||||
false
|
||||
false,
|
||||
)
|
||||
if (pureThemeEnabled) activity.theme.applyStyle(R.style.Pure, true)
|
||||
}
|
||||
@ -105,7 +105,7 @@ object ThemeHelper {
|
||||
context.packageManager.setComponentEnabledSetting(
|
||||
ComponentName(context.packageName, activityClass),
|
||||
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
|
||||
PackageManager.DONT_KILL_APP
|
||||
PackageManager.DONT_KILL_APP,
|
||||
)
|
||||
}
|
||||
|
||||
@ -115,7 +115,7 @@ object ThemeHelper {
|
||||
context.packageManager.setComponentEnabledSetting(
|
||||
ComponentName(context.packageName, newLogoActivityClass),
|
||||
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
|
||||
PackageManager.DONT_KILL_APP
|
||||
PackageManager.DONT_KILL_APP,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -32,7 +32,7 @@ object WindowHelper {
|
||||
// See: https://developer.android.com/training/system-ui/immersive#kotlin
|
||||
activity.toggleSystemBars(
|
||||
types = WindowInsetsCompat.Type.systemBars(),
|
||||
showBars = !isFullscreen
|
||||
showBars = !isFullscreen,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -6,5 +6,5 @@ import androidx.annotation.StringRes
|
||||
data class AppShortcut(
|
||||
val action: String,
|
||||
@StringRes val label: Int,
|
||||
@DrawableRes val drawable: Int
|
||||
@DrawableRes val drawable: Int,
|
||||
)
|
||||
|
@ -20,5 +20,5 @@ data class BackupFile(
|
||||
var playlistBookmarks: List<PlaylistBookmark>? = emptyList(),
|
||||
var localPlaylists: List<LocalPlaylistWithVideos>? = emptyList(),
|
||||
var preferences: List<PreferenceItem>? = emptyList(),
|
||||
var channelGroups: List<SubscriptionGroup>? = emptyList()
|
||||
var channelGroups: List<SubscriptionGroup>? = emptyList(),
|
||||
)
|
||||
|
@ -4,5 +4,5 @@ data class BottomSheetItem(
|
||||
val title: String,
|
||||
val drawable: Int? = null,
|
||||
val getCurrent: () -> String? = { null },
|
||||
val onClick: () -> Unit = {}
|
||||
val onClick: () -> Unit = {},
|
||||
)
|
||||
|
@ -5,7 +5,7 @@ import com.github.libretube.R
|
||||
|
||||
sealed class ChannelTabs(
|
||||
val identifierName: String,
|
||||
@IdRes val chipId: Int
|
||||
@IdRes val chipId: Int,
|
||||
) {
|
||||
object Playlists : ChannelTabs("playlists", R.id.playlists)
|
||||
object Shorts : ChannelTabs("shorts", R.id.shorts)
|
||||
|
@ -2,5 +2,5 @@ package com.github.libretube.obj
|
||||
|
||||
data class Country(
|
||||
val name: String,
|
||||
val code: String
|
||||
val code: String,
|
||||
)
|
||||
|
@ -9,7 +9,7 @@ sealed class DownloadStatus {
|
||||
data class Progress(
|
||||
val progress: Long,
|
||||
val downloaded: Long,
|
||||
val total: Long
|
||||
val total: Long,
|
||||
) : DownloadStatus()
|
||||
|
||||
data class Error(val message: String, val cause: Throwable? = null) : DownloadStatus()
|
||||
|
@ -7,5 +7,5 @@ data class DownloadedFile(
|
||||
val name: String,
|
||||
val size: Long,
|
||||
var metadata: Streams? = null,
|
||||
var thumbnail: Bitmap? = null
|
||||
var thumbnail: Bitmap? = null,
|
||||
)
|
||||
|
@ -7,5 +7,5 @@ data class ImportPlaylist(
|
||||
var name: String? = null,
|
||||
val type: String? = null,
|
||||
val visibility: String? = null,
|
||||
var videos: List<String> = listOf()
|
||||
var videos: List<String> = listOf(),
|
||||
)
|
||||
|
@ -6,5 +6,5 @@ import kotlinx.serialization.Serializable
|
||||
data class ImportPlaylistFile(
|
||||
val format: String,
|
||||
val version: Int,
|
||||
val playlists: List<ImportPlaylist> = emptyList()
|
||||
val playlists: List<ImportPlaylist> = emptyList(),
|
||||
)
|
||||
|
@ -7,5 +7,5 @@ import kotlinx.serialization.Serializable
|
||||
data class NewPipeSubscription(
|
||||
val name: String,
|
||||
@SerialName("service_id") val serviceId: Int,
|
||||
val url: String
|
||||
val url: String,
|
||||
)
|
||||
|
@ -7,5 +7,5 @@ import kotlinx.serialization.Serializable
|
||||
data class NewPipeSubscriptions(
|
||||
@SerialName("app_version") val appVersion: String = "",
|
||||
@SerialName("app_version_int") val appVersionInt: Int = 0,
|
||||
val subscriptions: List<NewPipeSubscription> = emptyList()
|
||||
val subscriptions: List<NewPipeSubscription> = emptyList(),
|
||||
)
|
||||
|
@ -6,5 +6,5 @@ data class PlayerNotificationData(
|
||||
val title: String? = null,
|
||||
val uploaderName: String? = null,
|
||||
val thumbnailUrl: String? = null,
|
||||
val thumbnailPath: Path? = null
|
||||
val thumbnailPath: Path? = null,
|
||||
)
|
||||
|
@ -7,5 +7,5 @@ import kotlinx.serialization.json.JsonPrimitive
|
||||
@Serializable
|
||||
data class PreferenceItem(
|
||||
val key: String? = null,
|
||||
val value: JsonPrimitive = JsonNull
|
||||
val value: JsonPrimitive = JsonNull,
|
||||
)
|
||||
|
@ -5,5 +5,5 @@ data class PreviewFrame(
|
||||
val positionX: Int,
|
||||
val positionY: Int,
|
||||
val framesPerPageX: Int,
|
||||
val framesPerPageY: Int
|
||||
val framesPerPageY: Int,
|
||||
)
|
||||
|
@ -4,5 +4,5 @@ data class ShareData(
|
||||
val currentChannel: String? = null,
|
||||
val currentVideo: String? = null,
|
||||
val currentPlaylist: String? = null,
|
||||
var currentPosition: Long? = null
|
||||
var currentPosition: Long? = null,
|
||||
)
|
||||
|
@ -2,5 +2,5 @@ package com.github.libretube.obj
|
||||
|
||||
data class VideoResolution(
|
||||
val name: String,
|
||||
val resolution: Int
|
||||
val resolution: Int,
|
||||
)
|
||||
|
@ -20,5 +20,5 @@ data class Asset(
|
||||
val state: String,
|
||||
@SerialName("updated_at") val updatedAt: Instant,
|
||||
val uploader: User,
|
||||
val url: String
|
||||
val url: String,
|
||||
)
|
||||
|
@ -12,5 +12,5 @@ data class Reactions(
|
||||
val laugh: Int,
|
||||
val rocket: Int,
|
||||
@SerialName("total_count") val totalCount: Int,
|
||||
val url: String
|
||||
val url: String,
|
||||
)
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user