From 0012ca2aace9ebf78be0c95c9318da251381b24d Mon Sep 17 00:00:00 2001 From: AbdallahMehiz Date: Sun, 9 Jun 2024 00:44:34 +0100 Subject: [PATCH] feat: mpv.conf settings --- app/build.gradle.kts | 1 + .../live/mehiz/mpvkt/di/PreferencesModule.kt | 2 + .../mpvkt/preferences/AdvancedPreferences.kt | 8 + .../mehiz/mpvkt/ui/player/PlayerActivity.kt | 31 +++- .../preferences/AdvancedPreferencesScreen.kt | 145 ++++++++++++++++++ .../mpvkt/ui/preferences/PreferencesScreen.kt | 8 +- app/src/main/res/values/strings.xml | 5 + gradle/libs.versions.toml | 1 + 8 files changed, 195 insertions(+), 6 deletions(-) create mode 100644 app/src/main/java/live/mehiz/mpvkt/preferences/AdvancedPreferences.kt create mode 100644 app/src/main/java/live/mehiz/mpvkt/ui/preferences/AdvancedPreferencesScreen.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 5ad6a33..ba045a0 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -91,6 +91,7 @@ dependencies { implementation(libs.androidx.constraintlayout) implementation(libs.androidx.compose.constraintlayout) implementation(libs.androidx.material3.icons.extended) + implementation(libs.androidx.documentfile) implementation(libs.aniyomi.mpv.lib) implementation(libs.aniyomi.ffmpeg.kit) diff --git a/app/src/main/java/live/mehiz/mpvkt/di/PreferencesModule.kt b/app/src/main/java/live/mehiz/mpvkt/di/PreferencesModule.kt index 904612d..afbf6c9 100644 --- a/app/src/main/java/live/mehiz/mpvkt/di/PreferencesModule.kt +++ b/app/src/main/java/live/mehiz/mpvkt/di/PreferencesModule.kt @@ -1,5 +1,6 @@ package live.mehiz.mpvkt.di +import live.mehiz.mpvkt.preferences.AdvancedPreferences import live.mehiz.mpvkt.preferences.AppearancePreferences import live.mehiz.mpvkt.preferences.AudioPreferences import live.mehiz.mpvkt.preferences.DecoderPreferences @@ -19,4 +20,5 @@ val PreferencesModule = module { singleOf(::DecoderPreferences) singleOf(::SubtitlesPreferences) singleOf(::AudioPreferences) + singleOf(::AdvancedPreferences) } diff --git a/app/src/main/java/live/mehiz/mpvkt/preferences/AdvancedPreferences.kt b/app/src/main/java/live/mehiz/mpvkt/preferences/AdvancedPreferences.kt new file mode 100644 index 0000000..c2b2033 --- /dev/null +++ b/app/src/main/java/live/mehiz/mpvkt/preferences/AdvancedPreferences.kt @@ -0,0 +1,8 @@ +package live.mehiz.mpvkt.preferences + +import live.mehiz.mpvkt.preferences.preference.PreferenceStore + +class AdvancedPreferences(preferenceStore: PreferenceStore) { + val mpvConfStorageUri = preferenceStore.getString("mpv_conf_storage_location_uri") + val mpvConf = preferenceStore.getString("mpv.conf") +} diff --git a/app/src/main/java/live/mehiz/mpvkt/ui/player/PlayerActivity.kt b/app/src/main/java/live/mehiz/mpvkt/ui/player/PlayerActivity.kt index ad944ad..d1874cf 100644 --- a/app/src/main/java/live/mehiz/mpvkt/ui/player/PlayerActivity.kt +++ b/app/src/main/java/live/mehiz/mpvkt/ui/player/PlayerActivity.kt @@ -12,9 +12,11 @@ import android.view.WindowManager import androidx.activity.enableEdgeToEdge import androidx.appcompat.app.AppCompatActivity import androidx.core.view.WindowCompat +import androidx.documentfile.provider.DocumentFile import `is`.xyz.mpv.MPVLib import `is`.xyz.mpv.Utils import live.mehiz.mpvkt.databinding.PlayerLayoutBinding +import live.mehiz.mpvkt.preferences.AdvancedPreferences import live.mehiz.mpvkt.preferences.AudioPreferences import live.mehiz.mpvkt.preferences.DecoderPreferences import live.mehiz.mpvkt.preferences.PlayerPreferences @@ -35,9 +37,10 @@ class PlayerActivity : AppCompatActivity() { private val decoderPreferences by inject() private val audioPreferences by inject() private val subtitlesPreferences by inject() + private val advancedPreferences by inject() override fun onCreate(savedInstanceState: Bundle?) { - if(playerPreferences.drawOverDisplayCutout.get()) enableEdgeToEdge() + if (playerPreferences.drawOverDisplayCutout.get()) enableEdgeToEdge() super.onCreate(savedInstanceState) window.setFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS) window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) @@ -77,24 +80,25 @@ class PlayerActivity : AppCompatActivity() { private fun setupMPV() { Utils.copyAssets(this) + copyMPVConfigFiles() player.initialize( applicationContext.filesDir.path, applicationContext.cacheDir.path, "v", - if(decoderPreferences.gpuNext.get()) "gpu-next" else "gpu" + if (decoderPreferences.gpuNext.get()) "gpu-next" else "gpu", ) MPVLib.setPropertyString( "hwdec", - if (decoderPreferences.tryHWDecoding.get()) "auto-copy" else "no" + if (decoderPreferences.tryHWDecoding.get()) "auto-copy" else "no", ) - when(decoderPreferences.debanding.get()) { + when (decoderPreferences.debanding.get()) { Debanding.None -> {} Debanding.CPU -> MPVLib.setPropertyString("vf", "gradfun=radius=12") Debanding.GPU -> MPVLib.setPropertyString("deband", "yes") } - if(decoderPreferences.useYUV420P.get()) MPVLib.setPropertyString("vf", "format=yuv420p") + if (decoderPreferences.useYUV420P.get()) MPVLib.setPropertyString("vf", "format=yuv420p") player.addObserver(PlayerObserver(this)) } @@ -107,6 +111,23 @@ class PlayerActivity : AppCompatActivity() { MPVLib.setPropertyString("slang", subtitlesPreferences.preferredLanguages.get()) } + private fun copyMPVConfigFiles() { + val applicationPath = applicationContext.filesDir.path + try { + DocumentFile.fromTreeUri(this, Uri.parse(advancedPreferences.mpvConfStorageUri.get()))!!.listFiles().forEach { + if (it.isDirectory) { + DocumentFile.fromFile(File(applicationPath)).createDirectory(it.name!!) + return@forEach + } + val input = contentResolver.openInputStream(it.uri) + input!!.copyTo(File(applicationPath + "/" + it.name).outputStream()) + } + } catch (e: Exception) { + File(applicationContext.filesDir.path + "/mpv.conf").writeText(advancedPreferences.mpvConf.get()) + Log.e("PlayerActivity", "Couldn't copy mpv configuration files: ${e.message}") + } + } + private fun parsePathFromIntent(intent: Intent): String? { val filepath: String? = when (intent.action) { Intent.ACTION_VIEW -> intent.data?.let { resolveUri(it) } diff --git a/app/src/main/java/live/mehiz/mpvkt/ui/preferences/AdvancedPreferencesScreen.kt b/app/src/main/java/live/mehiz/mpvkt/ui/preferences/AdvancedPreferencesScreen.kt new file mode 100644 index 0000000..ac03411 --- /dev/null +++ b/app/src/main/java/live/mehiz/mpvkt/ui/preferences/AdvancedPreferencesScreen.kt @@ -0,0 +1,145 @@ +package live.mehiz.mpvkt.ui.preferences + +import android.content.Intent +import android.net.Uri +import android.os.Environment +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.util.fastJoinToString +import androidx.documentfile.provider.DocumentFile +import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.navigator.LocalNavigator +import cafe.adriel.voyager.navigator.currentOrThrow +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import live.mehiz.mpvkt.R +import live.mehiz.mpvkt.preferences.AdvancedPreferences +import live.mehiz.mpvkt.preferences.preference.collectAsState +import me.zhanghai.compose.preference.ProvidePreferenceLocals +import me.zhanghai.compose.preference.TextFieldPreference +import me.zhanghai.compose.preference.preference +import org.koin.compose.koinInject +import kotlin.io.path.deleteIfExists +import kotlin.io.path.outputStream +import kotlin.io.path.readLines + +object AdvancedPreferencesScreen : Screen { + @OptIn(ExperimentalMaterial3Api::class) + @Composable + override fun Content() { + val context = LocalContext.current + val navigator = LocalNavigator.currentOrThrow + val preferences = koinInject() + + val getMPVConfLocation: (String) -> String = { + Environment.getExternalStorageDirectory().canonicalPath + "/" + Uri.decode(it).substringAfterLast(":") + } + Scaffold( + topBar = { + TopAppBar( + title = { + Text(stringResource(R.string.pref_advanced)) + }, + navigationIcon = { + IconButton(onClick = { navigator.pop() }) { + Icon(Icons.AutoMirrored.Default.ArrowBack, null) + } + }, + ) + }, + ) { padding -> + ProvidePreferenceLocals { + val locationPicker = rememberLauncherForActivityResult( + ActivityResultContracts.OpenDocumentTree(), + ) { uri -> + if (uri == null) return@rememberLauncherForActivityResult + + val flags = Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION + context.contentResolver.takePersistableUriPermission(uri, flags) + preferences.mpvConfStorageUri.set(uri.toString()) + } + val mpvConfStorageLocation by preferences.mpvConfStorageUri.collectAsState() + LazyColumn( + Modifier + .fillMaxSize() + .padding(padding), + ) { + preference( + "mpv_storage_location", + title = { Text(stringResource(R.string.pref_advanced_mpv_conf_storage_location)) }, + summary = { + if (mpvConfStorageLocation.isNotBlank()) { + Text(getMPVConfLocation(mpvConfStorageLocation)) + } + }, + onClick = { + locationPicker.launch(null) + }, + ) + item { + var mpvConf by remember { mutableStateOf(preferences.mpvConf.get()) } + LaunchedEffect(true) { + if (mpvConfStorageLocation.isBlank()) return@LaunchedEffect + withContext(Dispatchers.IO) { + val tempFile = kotlin.io.path.createTempFile() + runCatching { + val uri = DocumentFile.fromTreeUri( + context, + Uri.parse(mpvConfStorageLocation), + )!!.findFile("mpv.conf")!!.uri + context.contentResolver.openInputStream(uri)?.copyTo(tempFile.outputStream()) + preferences.mpvConf.set(tempFile.readLines().fastJoinToString("\n")) + } + tempFile.deleteIfExists() + } + } + TextFieldPreference( + value = mpvConf, + onValueChange = { mpvConf = it }, + title = { Text(stringResource(R.string.pref_advanced_mpv_conf)) }, + textToValue = { + preferences.mpvConf.set(it) + if (mpvConfStorageLocation.isNotBlank()) { + val tree = DocumentFile.fromTreeUri(context, Uri.parse(mpvConfStorageLocation))!! + val uri = if (tree.findFile("mpv.conf") == null) { + val conf = tree.createFile("text/plain", "mpv.conf")!! + conf.renameTo("mpv.conf") + conf.uri + } else { + tree.findFile("mpv.conf")!!.uri + } + val out = context.contentResolver.openOutputStream(uri) + out!!.write(it.toByteArray()) + out.flush() + out.close() + } + it + }, + summary = { Text(mpvConf.lines()[0]) }, + ) + } + } + } + } + } +} diff --git a/app/src/main/java/live/mehiz/mpvkt/ui/preferences/PreferencesScreen.kt b/app/src/main/java/live/mehiz/mpvkt/ui/preferences/PreferencesScreen.kt index c4929dc..3918c99 100644 --- a/app/src/main/java/live/mehiz/mpvkt/ui/preferences/PreferencesScreen.kt +++ b/app/src/main/java/live/mehiz/mpvkt/ui/preferences/PreferencesScreen.kt @@ -6,6 +6,7 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.outlined.ArrowBack import androidx.compose.material.icons.outlined.Audiotrack +import androidx.compose.material.icons.outlined.Code import androidx.compose.material.icons.outlined.Memory import androidx.compose.material.icons.outlined.Palette import androidx.compose.material.icons.outlined.PlayCircle @@ -25,7 +26,6 @@ import cafe.adriel.voyager.navigator.currentOrThrow import live.mehiz.mpvkt.R import me.zhanghai.compose.preference.ProvidePreferenceLocals import me.zhanghai.compose.preference.preference -import me.zhanghai.compose.preference.preferenceTheme object PreferencesScreen : Screen { @OptIn(ExperimentalMaterial3Api::class) @@ -80,6 +80,12 @@ object PreferencesScreen : Screen { icon = { Icon(Icons.Outlined.Audiotrack, null) }, onClick = { navigator.push(AudioPreferencesScreen) }, ) + preference( + key = "advanced", + title = { Text(text = stringResource(R.string.pref_advanced)) }, + icon = { Icon(Icons.Outlined.Code, null) }, + onClick = { navigator.push(AdvancedPreferencesScreen) } + ) } } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1aa78bd..165a02a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -48,6 +48,11 @@ Audio + Advanced + pick mpv configuration storage location + Edit mpv.conf + Edit input.conf + Add external subtitles Add external audio tracks Off diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3407daa..ed0022e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -32,6 +32,7 @@ androidx-recyclerview = { group = "androidx.recyclerview", name = "recyclerview" androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version = "2.1.4" } androidx-material3-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended", version = "1.6.7" } androidx-compose-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout-compose", version = "1.0.1" } +androidx-documentfile = { group = "androidx.documentfile", name = "documentfile", version = "1.0.1" } aniyomi-mpv-lib = { module = "com.github.aniyomiorg:aniyomi-mpv-lib", version = "1.15.n" } aniyomi-ffmpeg-kit = { module = "com.github.jmir1:ffmpeg-kit", version = "1.15" }