diff --git a/.github/ISSUE_TEMPLATE/00-bug.yml b/.github/ISSUE_TEMPLATE/00-bug.yml
index 1aab31da..27e43e4b 100644
--- a/.github/ISSUE_TEMPLATE/00-bug.yml
+++ b/.github/ISSUE_TEMPLATE/00-bug.yml
@@ -1,6 +1,6 @@
name: "Bug Report"
description: Report a bug
-title: "
"
+title: ""
labels: [
"bug"
]
@@ -23,6 +23,16 @@ body:
3. See error
validations:
required: false
+ - type: dropdown
+ id: experimental
+ attributes:
+ label: Experimental settings
+ description: Do you have experiment settings turn on?
+ options:
+ - "Yes"
+ - "No"
+ validations:
+ required: true
- type: input
id: app-version
attributes:
diff --git a/.github/ISSUE_TEMPLATE/01-playback.yml b/.github/ISSUE_TEMPLATE/01-playback.yml
index cbb287b5..00ee97f3 100644
--- a/.github/ISSUE_TEMPLATE/01-playback.yml
+++ b/.github/ISSUE_TEMPLATE/01-playback.yml
@@ -1,6 +1,6 @@
name: "Playback Problem"
description: Report an issue with playback
-title: ""
+title: ""
labels: [
"bug", "playback"
]
@@ -12,12 +12,14 @@ body:
description: Please enter a description of the playback issue
validations:
required: true
- - type: textarea
- id: mediainfo
+ - type: dropdown
+ id: experimental
attributes:
- label: "Media info"
- description: Please enter details about what media you are trying to play (video or audio codecs, container, etc)
- placeholder: Use mediainfo or "Send media info to server" in the app to gather this
+ label: Experimental settings
+ description: Do you have experiment settings turn on?
+ options:
+ - "Yes"
+ - "No"
validations:
required: true
- type: dropdown
@@ -32,6 +34,14 @@ body:
- External App (please specify)
validations:
required: true
+ - type: textarea
+ id: mediainfo
+ attributes:
+ label: "Media info"
+ description: Please enter details about what media you are trying to play (video or audio codecs, container, etc)
+ placeholder: Use mediainfo or "Send media info to server" in the app to gather this
+ validations:
+ required: true
- type: input
id: app-version
attributes:
diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md
index bdc7dfa5..0d818e21 100644
--- a/DEVELOPMENT.md
+++ b/DEVELOPMENT.md
@@ -97,3 +97,9 @@ Some of the models, such as `GetItemsFilter` or `ViewOptions`, are stored as JSO
These settings are similar to Room ones and are usually stored per user. These can be accessed via `KeyValueService`. The values are `@Serializable` objects saved as JSON.
This should be used sparingly.
+
+#### Experimental
+
+Some settings are considered "experimental". These may cause instabilities or may need further testing. They may be changed or removed any time.
+
+These are stored in the `ExperimentalPreferences` object in `AppPreferences`. Accessing them should always be gated with `ExperimentalPreferences.enabled`. There are a few helper functions for this purpose.
diff --git a/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreference.kt b/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreference.kt
index 8149f8b6..f573c4a7 100644
--- a/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreference.kt
+++ b/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreference.kt
@@ -1266,8 +1266,16 @@ val advancedPreferences =
AppPreference.DebugLogging,
AppPreference.ImageDiskCacheSize,
AppPreference.ClearImageCache,
+ ExperimentalPreference.Enable,
AppPreference.OssLicenseInfo,
),
+ conditionalPreferences =
+ listOf(
+ ConditionalPreferences(
+ condition = { it.experimentalPreferences.enabled },
+ preferences = listOf(ExperimentalPreference.ExperimentalSettings),
+ ),
+ ),
),
)
}
diff --git a/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreferencesSerializer.kt b/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreferencesSerializer.kt
index ddac936a..1e84663d 100644
--- a/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreferencesSerializer.kt
+++ b/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreferencesSerializer.kt
@@ -174,6 +174,14 @@ class AppPreferencesSerializer
showLyrics = true
showAlbumArt = true
}.build()
+
+ experimentalPreferences =
+ ExperimentalPreferences
+ .newBuilder()
+ .apply {
+ enabled = false
+ videoTunnelingEnabled = false
+ }.build()
}.build()
override suspend fun readFrom(input: InputStream): AppPreferences {
@@ -252,6 +260,11 @@ inline fun AppPreferences.updateMusicPreferences(block: MusicPreferences.Builder
musicPreferences = musicPreferences.toBuilder().apply(block).build()
}
+inline fun AppPreferences.updateExperimentalPreferences(block: ExperimentalPreferences.Builder.() -> Unit): AppPreferences =
+ update {
+ experimentalPreferences = experimentalPreferences.toBuilder().apply(block).build()
+ }
+
fun SubtitlePreferences.Builder.resetSubtitles() {
fontSize = SubtitleSettings.FontSize.defaultValue.toInt()
fontColor = SubtitleSettings.FontColor.defaultValue.toArgb()
diff --git a/app/src/main/java/com/github/damontecres/wholphin/preferences/ExperimentalPreference.kt b/app/src/main/java/com/github/damontecres/wholphin/preferences/ExperimentalPreference.kt
new file mode 100644
index 00000000..18e38f79
--- /dev/null
+++ b/app/src/main/java/com/github/damontecres/wholphin/preferences/ExperimentalPreference.kt
@@ -0,0 +1,58 @@
+package com.github.damontecres.wholphin.preferences
+
+import com.github.damontecres.wholphin.R
+import com.github.damontecres.wholphin.ui.nav.Destination
+import com.github.damontecres.wholphin.ui.preferences.PreferenceGroup
+import com.github.damontecres.wholphin.ui.preferences.PreferenceScreenOption
+
+object ExperimentalPreference {
+ val Enable =
+ AppSwitchPreference(
+ title = R.string.experimental_settings,
+ defaultValue = false,
+ getter = { it.experimentalPreferences.enabled },
+ setter = { prefs, value ->
+ prefs.updateExperimentalPreferences { enabled = value }
+ },
+ summaryOn = R.string.enabled,
+ summaryOff = R.string.disabled,
+ )
+
+ val ExperimentalSettings =
+ AppDestinationPreference(
+ title = R.string.experimental_settings,
+ destination = Destination.Settings(PreferenceScreenOption.EXPERIMENTAL),
+ )
+
+ val VideoTunneling =
+ AppSwitchPreference(
+ title = R.string.video_tunneling,
+ defaultValue = false,
+ getter = { it.experimentalPreferences.videoTunnelingEnabled },
+ setter = { prefs, value ->
+ prefs.updateExperimentalPreferences { videoTunnelingEnabled = value }
+ },
+ summaryOn = R.string.enabled,
+ summaryOff = R.string.disabled,
+ )
+}
+
+val experimentalPreferences =
+ buildList {
+ add(
+ PreferenceGroup(
+ title = R.string.experimental_settings,
+ preferences =
+ listOf(
+ ExperimentalPreference.VideoTunneling,
+ ),
+ ),
+ )
+ }
+
+/**
+ * Get a value from [ExperimentalPreference] or null if not enabled
+ */
+fun ExperimentalPreferences.get(block: ExperimentalPreferences.() -> T): T? = if (enabled) block.invoke(this) else null
+
+fun ExperimentalPreferences.enabled(block: ExperimentalPreferences.() -> Boolean): Boolean = enabled && block.invoke(this)
diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/PlayerFactory.kt b/app/src/main/java/com/github/damontecres/wholphin/services/PlayerFactory.kt
index 01b82777..24736015 100644
--- a/app/src/main/java/com/github/damontecres/wholphin/services/PlayerFactory.kt
+++ b/app/src/main/java/com/github/damontecres/wholphin/services/PlayerFactory.kt
@@ -26,10 +26,11 @@ import androidx.media3.exoplayer.video.VideoRendererEventListener
import androidx.media3.extractor.DefaultExtractorsFactory
import androidx.media3.session.MediaSession
import com.github.damontecres.wholphin.mpv.MpvPlayer
+import com.github.damontecres.wholphin.preferences.AppPreferences
import com.github.damontecres.wholphin.preferences.AssPlaybackMode
import com.github.damontecres.wholphin.preferences.MediaExtensionStatus
-import com.github.damontecres.wholphin.preferences.PlaybackPreferences
import com.github.damontecres.wholphin.preferences.PlayerBackend
+import com.github.damontecres.wholphin.preferences.get
import com.github.damontecres.wholphin.services.hilt.AuthOkHttpClient
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.hilt.android.qualifiers.ApplicationContext
@@ -61,8 +62,9 @@ class PlayerFactory
suspend fun createVideoPlayer(
backend: PlayerBackend,
- prefs: PlaybackPreferences,
+ appPreferences: AppPreferences,
): PlayerCreation {
+ val prefs = appPreferences.playbackPreferences
withContext(WholphinDispatchers.Main) {
if (currentPlayer?.isReleased == false) {
Timber.w("Player was not released before trying to create a new one!")
@@ -105,6 +107,7 @@ class PlayerFactory
WholphinRenderersFactory(context, decodeAv1)
.setEnableDecoderFallback(true)
.setExtensionRendererMode(rendererMode)
+
val mediaSourceFactory =
if (useLibAss) {
val renderType =
@@ -129,7 +132,9 @@ class PlayerFactory
extractorsFactory,
)
}
- val trackSelector = createTrackSelector()
+ val tunneling =
+ appPreferences.experimentalPreferences.get { videoTunnelingEnabled }
+ val trackSelector = createTrackSelector(tunneling)
ExoPlayer
.Builder(context)
@@ -200,11 +205,13 @@ class PlayerFactory
.setConstantBitrateSeekingEnabled(true)
.setConstantBitrateSeekingAlwaysEnabled(true)
- private fun createTrackSelector() =
+ private fun createTrackSelector(tunneling: Boolean? = null) =
DefaultTrackSelector(context).apply {
setParameters(
buildUponParameters()
- .setAudioOffloadPreferences(
+ .apply {
+ tunneling?.let { setTunnelingEnabled(tunneling) }
+ }.setAudioOffloadPreferences(
AudioOffloadPreferences
.Builder()
.setAudioOffloadMode(AudioOffloadPreferences.AUDIO_OFFLOAD_MODE_ENABLED)
diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackViewModel.kt
index b297c0c6..37d3e5ad 100644
--- a/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackViewModel.kt
+++ b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackViewModel.kt
@@ -253,7 +253,7 @@ class PlaybackViewModel
val playerCreation =
playerFactory.createVideoPlayer(
playerBackend,
- preferences.appPreferences.playbackPreferences,
+ preferences.appPreferences,
)
this.player = playerCreation.player
currentPlayer.update {
diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/PreferenceUtils.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/PreferenceUtils.kt
index 06399fe9..0a843dcb 100644
--- a/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/PreferenceUtils.kt
+++ b/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/PreferenceUtils.kt
@@ -37,6 +37,7 @@ enum class PreferenceScreenOption {
MPV,
SCREENSAVER,
SKIP_SEGMENTS,
+ EXPERIMENTAL,
;
companion object {
diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/PreferencesContent.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/PreferencesContent.kt
index 982e2537..b08e9508 100644
--- a/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/PreferencesContent.kt
+++ b/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/PreferencesContent.kt
@@ -48,13 +48,16 @@ import coil3.imageLoader
import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.preferences.AppPreference
import com.github.damontecres.wholphin.preferences.AppPreferences
+import com.github.damontecres.wholphin.preferences.AppSwitchPreference
import com.github.damontecres.wholphin.preferences.ExoPlayerPreferences
+import com.github.damontecres.wholphin.preferences.ExperimentalPreference
import com.github.damontecres.wholphin.preferences.MpvPreferences
import com.github.damontecres.wholphin.preferences.PlayerBackend
import com.github.damontecres.wholphin.preferences.ScreensaverPreference
import com.github.damontecres.wholphin.preferences.SkipSegmentPreferences
import com.github.damontecres.wholphin.preferences.advancedPreferences
import com.github.damontecres.wholphin.preferences.basicPreferences
+import com.github.damontecres.wholphin.preferences.experimentalPreferences
import com.github.damontecres.wholphin.preferences.screensaverPreferences
import com.github.damontecres.wholphin.preferences.updatePlaybackPreferences
import com.github.damontecres.wholphin.services.Release
@@ -148,6 +151,7 @@ fun PreferencesContent(
PreferenceScreenOption.MPV -> MpvPreferences
PreferenceScreenOption.SCREENSAVER -> screensaverPreferences
PreferenceScreenOption.SKIP_SEGMENTS -> SkipSegmentPreferences
+ PreferenceScreenOption.EXPERIMENTAL -> experimentalPreferences
}
val screenTitle =
when (preferenceScreenOption) {
@@ -157,6 +161,7 @@ fun PreferencesContent(
PreferenceScreenOption.MPV -> R.string.mpv_options
PreferenceScreenOption.SCREENSAVER -> R.string.screensaver_settings
PreferenceScreenOption.SKIP_SEGMENTS -> R.string.skip_behavior
+ PreferenceScreenOption.EXPERIMENTAL -> R.string.experimental_settings
}
var visible by remember { mutableStateOf(false) }
@@ -569,6 +574,46 @@ fun PreferencesContent(
)
}
+ ExperimentalPreference.Enable -> {
+ var showConfirm by remember { mutableStateOf(false) }
+ pref as AppSwitchPreference
+ val value = pref.getter.invoke(preferences)
+ SwitchPreference(
+ title = stringResource(pref.title),
+ value = value,
+ onClick = {
+ if (value) {
+ scope.launch(ExceptionHandler()) {
+ preferences =
+ viewModel.preferenceDataStore.updateData { prefs ->
+ pref.setter.invoke(prefs, false)
+ }
+ }
+ } else {
+ showConfirm = true
+ }
+ },
+ summaryOn = pref.summaryOn?.let { stringResource(pref.summaryOn) },
+ summaryOff = pref.summaryOff?.let { stringResource(pref.summaryOff) },
+ )
+ if (showConfirm) {
+ ConfirmDialog(
+ title = stringResource(R.string.confirm_enable_experimental_title),
+ body = stringResource(R.string.confirm_enable_experimental_body),
+ onCancel = { showConfirm = false },
+ onConfirm = {
+ showConfirm = false
+ scope.launch(ExceptionHandler()) {
+ preferences =
+ viewModel.preferenceDataStore.updateData { prefs ->
+ pref.setter.invoke(prefs, true)
+ }
+ }
+ },
+ )
+ }
+ }
+
else -> {
val value = pref.getter.invoke(preferences)
ComposablePreference(
@@ -762,6 +807,7 @@ fun PreferencesPage(
PreferenceScreenOption.MPV,
PreferenceScreenOption.SCREENSAVER,
PreferenceScreenOption.SKIP_SEGMENTS,
+ PreferenceScreenOption.EXPERIMENTAL,
-> {
PreferencesContent(
initialPreferences,
diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/slideshow/SlideshowViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/slideshow/SlideshowViewModel.kt
index 8db01751..c81ffcb7 100644
--- a/app/src/main/java/com/github/damontecres/wholphin/ui/slideshow/SlideshowViewModel.kt
+++ b/app/src/main/java/com/github/damontecres/wholphin/ui/slideshow/SlideshowViewModel.kt
@@ -111,7 +111,7 @@ class SlideshowViewModel
val playerCreation =
playerFactory.createVideoPlayer(
backend = PlayerBackend.EXO_PLAYER,
- appPreferences.playbackPreferences,
+ appPreferences,
)
player = playerCreation.player
player.addListener(this@SlideshowViewModel)
diff --git a/app/src/main/proto/WholphinDataStore.proto b/app/src/main/proto/WholphinDataStore.proto
index d2443c60..2eb47cef 100644
--- a/app/src/main/proto/WholphinDataStore.proto
+++ b/app/src/main/proto/WholphinDataStore.proto
@@ -212,6 +212,11 @@ message MusicPreferences {
bool show_backdrop = 4;
}
+message ExperimentalPreferences {
+ bool enabled = 1;
+ bool video_tunneling_enabled = 2;
+}
+
message AppPreferences {
// The currently signed in server and user IDs, mostly for restoring a session
string current_server_id = 1;
@@ -229,4 +234,5 @@ message AppPreferences {
bool sign_in_automatically = 11;
PhotoPreferences photo_preferences = 12;
MusicPreferences music_preferences = 13;
+ ExperimentalPreferences experimental_preferences = 14;
}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index dd999158..312f78b2 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -829,4 +829,8 @@
Popularity
Production status
Use different style for HDR
+ Experimental settings
+ Enable experimental settings?
+ Experimental settings may be unstable!\n\n These may removed or changed in any release.
+ Video tunneling