vc=49: Auto-start playback setting (cold-open autoplay)

Settings → Autoplay section now has a third toggle: Auto-start
playback (default on). When on, opening a fresh video starts
playing immediately on the detail page. When off, the page renders
with the thumbnail + Play overlay and you tap to start.

Independent of the end-of-queue autoplay mode and the back-from-
fullscreen behavior (that already auto-resumes because the
controller is mid-stream — preserved).

Implementation: a single OR into the initial inlinePlaying state in
VideoDetailScreen.
This commit is contained in:
Kayos 2026-05-26 07:17:29 -07:00
parent 62cc18c940
commit 0f946d8b4e
4 changed files with 61 additions and 11 deletions

View file

@ -55,6 +55,6 @@ const val NEWPIPE_APPLICATION_ID_NEW = "net.newpipe.app"
// vc=19 / 0.1.0-AE — rust pipeline cutover. Extraction via
// strawcore-core (Sulkta-Coop/strawcore) via the UniFFI wrapper; no
// NewPipeExtractor in the runtime path.
const val STRAW_VERSION_CODE = 48
const val STRAW_VERSION_NAME = "0.1.0-BH"
const val STRAW_VERSION_CODE = 49
const val STRAW_VERSION_NAME = "0.1.0-BI"
const val STRAW_APPLICATION_ID = "com.sulkta.straw"

View file

@ -65,6 +65,7 @@ private const val KEY_THEME = "theme_mode_v1"
private const val KEY_CACHE_ENABLED = "cache_enabled_v1"
private const val KEY_AUTOPLAY_MODE = "autoplay_mode_v1"
private const val KEY_AUTOPLAY_SKIP_WATCHED = "autoplay_skip_watched_v1"
private const val KEY_AUTOSTART_PLAYBACK = "autostart_playback_v1"
class SettingsStore(context: Context) {
private val sp: SharedPreferences = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
@ -89,6 +90,19 @@ class SettingsStore(context: Context) {
)
val autoplaySkipWatched: StateFlow<Boolean> = _autoplaySkipWatched.asStateFlow()
/**
* "Open a video → it starts playing immediately." Default on
* matches YT/NewPipe. When off, opening a fresh video lands you
* on the detail page with the thumbnail + Play overlay; you tap
* to start. Doesn't affect back-from-fullscreen (that's a
* separate path in VideoDetailScreen that defaults to true when
* the shared controller is already streaming the URL).
*/
private val _autoStartPlayback = MutableStateFlow(
sp.getBoolean(KEY_AUTOSTART_PLAYBACK, true),
)
val autoStartPlayback: StateFlow<Boolean> = _autoStartPlayback.asStateFlow()
fun toggle(cat: SbCategory) {
// Atomic toggle via updateAndGet — see AUD-HIGH note in HistoryStore.
val next = _sbCategories.updateAndGet { cur ->
@ -137,6 +151,13 @@ class SettingsStore(context: Context) {
sp.edit().putBoolean(KEY_AUTOPLAY_SKIP_WATCHED, skip).apply()
}
fun setAutoStartPlayback(autoStart: Boolean) {
val before = _autoStartPlayback.value
if (before == autoStart) return
_autoStartPlayback.value = autoStart
sp.edit().putBoolean(KEY_AUTOSTART_PLAYBACK, autoStart).apply()
}
private fun loadCategories(): Set<SbCategory> {
val raw = sp.getStringSet(KEY_SB_CATS, null)
return if (raw == null) {

View file

@ -133,16 +133,19 @@ fun VideoDetailScreen(
)
}
// Inline-play state resets when navigating to a different video.
// BUT: if the shared MediaController is already playing this exact
// stream — most commonly because the user popped back from
// fullscreen Player — default to true so the inline surface picks
// up the running playback instead of dropping back to the
// thumbnail+Play placeholder. Without this, system back from
// fullscreen looked like "the video went to background" — audio
// continued via the persistent controller but the video page
// re-rendered as a freshly-loaded detail.
// Defaults to TRUE when:
// * the shared MediaController is already streaming this URL
// (back-from-fullscreen — without this the page renders as
// "freshly loaded" while audio keeps playing in the
// background), or
// * the user has Settings → Auto-start playback enabled (cold
// open from search / subs / wherever immediately plays).
// Off + fresh URL → thumbnail + Play overlay, user taps to start.
val autoStart by Settings.get().autoStartPlayback.collectAsState()
var inlinePlaying by remember(streamUrl) {
mutableStateOf(NowPlaying.current.value?.streamUrl == streamUrl)
mutableStateOf(
NowPlaying.current.value?.streamUrl == streamUrl || autoStart,
)
}
LaunchedEffect(streamUrl) { vm.load(streamUrl) }

View file

@ -242,6 +242,32 @@ fun SettingsScreen() {
onCheckedChange = { store.setAutoplaySkipWatched(it) },
)
}
val autoStartPlayback by store.autoStartPlayback.collectAsState()
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 6.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
) {
Column(modifier = Modifier.weight(1f)) {
Text(
"Auto-start playback",
style = MaterialTheme.typography.bodyLarge,
fontWeight = FontWeight.SemiBold,
)
Text(
"Open a video → it starts immediately. Off: tap " +
"the thumbnail to start.",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
Switch(
checked = autoStartPlayback,
onCheckedChange = { store.setAutoStartPlayback(it) },
)
}
Spacer(modifier = Modifier.height(32.dp))
Text(