feat(android): promote the low-latency pipeline to default
apple / swift (push) Successful in 1m6s
android / android (push) Successful in 4m58s
arch / build-publish (push) Successful in 5m59s
ci / web (push) Successful in 51s
ci / rust (push) Successful in 2m29s
ci / docs-site (push) Successful in 1m0s
windows-host / package (push) Successful in 8m15s
release / apple (push) Successful in 8m19s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 1m24s
windows-msix / package (x64, C:\Users\Public\ffmpeg, x86_64-pc-windows-msvc, C:\t) (push) Successful in 1m17s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 54s
ci / bench (push) Successful in 5m14s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 1m1s
deb / build-publish (push) Successful in 5m0s
decky / build-publish (push) Successful in 15s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 6s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 5s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 4s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 54s
apple / screenshots (push) Successful in 5m49s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 2m23s
flatpak / build-publish (push) Successful in 4m39s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Failing after 8m48s
docker / deploy-docs (push) Successful in 19s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Failing after 8m12s
apple / swift (push) Successful in 1m6s
android / android (push) Successful in 4m58s
arch / build-publish (push) Successful in 5m59s
ci / web (push) Successful in 51s
ci / rust (push) Successful in 2m29s
ci / docs-site (push) Successful in 1m0s
windows-host / package (push) Successful in 8m15s
release / apple (push) Successful in 8m19s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 1m24s
windows-msix / package (x64, C:\Users\Public\ffmpeg, x86_64-pc-windows-msvc, C:\t) (push) Successful in 1m17s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 54s
ci / bench (push) Successful in 5m14s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 1m1s
deb / build-publish (push) Successful in 5m0s
decky / build-publish (push) Successful in 15s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 6s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 5s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 4s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 54s
apple / screenshots (push) Successful in 5m49s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 2m23s
flatpak / build-publish (push) Successful in 4m39s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Failing after 8m48s
docker / deploy-docs (push) Successful in 19s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Failing after 8m12s
The async decode loop (burst-feed + present-newest-per-vsync, the Apple client's discipline) plus the per-SoC tuning was gated behind an experimental, default-off toggle after the5dc24a0overhaul regressed on some phones. That regression was the receive-side latency ratchet the async loop fed — a standing queue that only grew — now fixed in the shared core (FrameChannel jumps to live instead of accumulating it), so the fast pipeline is the default again. Default the master toggle on via a fresh pref key (low_latency_mode_v2), mirroring the migrationda376b31used to flip it off: a new key re-defaults every install — including ones persisted off under the old key — to on, so the promotion reaches users who had saved settings, not just fresh installs. Both stale keys are abandoned unread. Toggle-off still restores the original synchronous decode pipeline byte-for-byte as a per-device escape hatch. Drop "(experimental)" from the settings labels and fix the now-stale default-off wording in the native + Kotlin docs. No decode-path routing change — run_async is reached simply because the toggle now defaults on. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -327,7 +327,7 @@ private fun buildSettingsRows(s: Settings, update: (Settings) -> Unit): List<GpR
|
||||
) { update(s.copy(hdrEnabled = it)) },
|
||||
toggle(
|
||||
"lowLatency", null, "Low-latency mode",
|
||||
"Experimental — aggressive decoder and system tuning. Turn off if the stream stutters or glitches.",
|
||||
"The fast pipeline (async decode + system tuning). On by default — turn off to fall back if the stream stutters or glitches.",
|
||||
s.lowLatencyMode,
|
||||
) { update(s.copy(lowLatencyMode = it)) },
|
||||
|
||||
|
||||
@@ -55,14 +55,18 @@ data class Settings(
|
||||
*/
|
||||
val libraryEnabled: Boolean = true,
|
||||
/**
|
||||
* "Low-latency mode (experimental)" — the master switch over the latency overhaul: decoder
|
||||
* ranking + per-SoC vendor keys + the async decode loop (native), pipeline thread boosts + ADPF
|
||||
* max-performance, game-tagged AAudio, DSCP marking on the media sockets, HDMI ALLM, and the
|
||||
* forced TV mode switch. (The Wi-Fi locks are NOT part of this — both are always held while
|
||||
* streaming; see StreamScreen.) Off (default): the original decode pipeline, kept as the
|
||||
* known-good baseline until the aggressive stack is proven per-device.
|
||||
* "Low-latency mode" — the master switch over the latency pipeline: the async decode loop
|
||||
* (native; burst-feed + present-newest-per-vsync, the Apple client's discipline), decoder ranking
|
||||
* + per-SoC vendor keys, pipeline thread boosts + ADPF max-performance, game-tagged AAudio, DSCP
|
||||
* marking on the media sockets, HDMI ALLM, and the forced TV mode switch. (The Wi-Fi locks are NOT
|
||||
* part of this — both are always held while streaming; see StreamScreen.) On (default): the fast
|
||||
* pipeline. Off restores the original synchronous decode loop byte-for-byte, kept as a per-device
|
||||
* escape hatch. Promoted to default once the receive-side latency ratchet the overhaul interacted
|
||||
* badly with was fixed in the shared core — the pump now jumps to live on a standing backlog
|
||||
* instead of accumulating it (see `punktfunk-core` `FrameChannel`), so the async loop no longer
|
||||
* feeds a queue that only grows.
|
||||
*/
|
||||
val lowLatencyMode: Boolean = false,
|
||||
val lowLatencyMode: Boolean = true,
|
||||
/**
|
||||
* Wake-on-LAN a saved host before connecting when it isn't currently seen on mDNS. On (default):
|
||||
* a connect to a host with a learned MAC that isn't advertising sends a magic packet and waits
|
||||
@@ -100,7 +104,7 @@ class SettingsStore(context: Context) {
|
||||
?: if (prefs.getBoolean(K_TRACKPAD, true)) TouchMode.TRACKPAD else TouchMode.POINTER,
|
||||
gamepadUiEnabled = prefs.getBoolean(K_GAMEPAD_UI, true),
|
||||
libraryEnabled = prefs.getBoolean(K_LIBRARY, true),
|
||||
lowLatencyMode = prefs.getBoolean(K_LOW_LATENCY, false),
|
||||
lowLatencyMode = prefs.getBoolean(K_LOW_LATENCY, true),
|
||||
autoWakeEnabled = prefs.getBoolean(K_AUTO_WAKE, true),
|
||||
)
|
||||
|
||||
@@ -142,12 +146,16 @@ class SettingsStore(context: Context) {
|
||||
const val K_LIBRARY = "library_enabled"
|
||||
|
||||
/**
|
||||
* Deliberately NOT the original `"low_latency_mode"` key: that one shipped default-ON, so
|
||||
* any install that ever saved settings persisted `true` — under the old key, flipping the
|
||||
* default to off would leave exactly the regressed devices stuck on the overhaul. The fresh
|
||||
* key restarts everyone at the safe default; the stale one is abandoned unread.
|
||||
* Bumped AGAIN to restart every install at the new default (ON). History: the original
|
||||
* `"low_latency_mode"` shipped default-ON; `"low_latency_mode_experimental"` restarted
|
||||
* everyone at OFF after the overhaul regressed on some phones. That regression was the
|
||||
* receive-side latency ratchet the async loop fed (a standing queue that only grew) — now
|
||||
* fixed in the shared core (`punktfunk-core` `FrameChannel`: the pump jumps to live on a
|
||||
* standing backlog instead of accumulating it), so the fast pipeline is the default again. A
|
||||
* fresh key re-defaults every install — including ones persisted OFF under the old key — to
|
||||
* on; both stale keys are abandoned unread. The toggle stays as a per-device escape hatch.
|
||||
*/
|
||||
const val K_LOW_LATENCY = "low_latency_mode_experimental"
|
||||
const val K_LOW_LATENCY = "low_latency_mode_v2"
|
||||
const val K_AUTO_WAKE = "auto_wake_enabled"
|
||||
|
||||
/** Legacy Boolean the enum replaced — read once as the migration default, never written. */
|
||||
|
||||
@@ -326,10 +326,10 @@ private fun DisplaySettings(s: Settings, update: (Settings) -> Unit, context: an
|
||||
) { c -> update(s.copy(compositor = c)) }
|
||||
|
||||
ToggleRow(
|
||||
title = "Low-latency mode (experimental)",
|
||||
subtitle = "Aggressive decoder and system tuning (per-device decoder selection, async " +
|
||||
"decode, HDMI game mode). Can lower latency, but may stutter or glitch on " +
|
||||
"some devices — turn off if the stream misbehaves.",
|
||||
title = "Low-latency mode",
|
||||
subtitle = "The fast pipeline (async decode, per-device decoder selection, HDMI game " +
|
||||
"mode). On by default — turn off to fall back to the plain decode path if the stream " +
|
||||
"stutters or glitches on this device.",
|
||||
checked = s.lowLatencyMode,
|
||||
onCheckedChange = { on -> update(s.copy(lowLatencyMode = on)) },
|
||||
)
|
||||
|
||||
@@ -65,10 +65,10 @@ fun StreamScreen(handle: Long, micEnabled: Boolean, onDisconnect: () -> Unit) {
|
||||
var showStats by remember { mutableStateOf(initialSettings.statsHudEnabled) }
|
||||
// Touch model is fixed per session (re-keys the gesture handler below if it ever changes).
|
||||
val touchMode = initialSettings.touchMode
|
||||
// "Low-latency mode (experimental)" master toggle, resolved once for the session. Off (the
|
||||
// default) runs the original decode pipeline; on enables the aggressive stack — decoder
|
||||
// ranking + vendor keys + async loop (native side), HDMI ALLM below, game-tagged audio, and
|
||||
// DSCP marking (applied earlier, at connect).
|
||||
// "Low-latency mode" master toggle, resolved once for the session. On (the default) enables the
|
||||
// fast pipeline — decoder ranking + vendor keys + async loop (native side), HDMI ALLM below,
|
||||
// game-tagged audio, and DSCP marking (applied earlier, at connect); off falls back to the
|
||||
// original synchronous decode pipeline as a per-device escape hatch.
|
||||
val lowLatencyMode = initialSettings.lowLatencyMode
|
||||
// TV form factor (leanback): the decoder actively switches the HDMI output mode to the stream
|
||||
// refresh; a phone/tablet gets the softer seamless frame-rate hint instead.
|
||||
|
||||
@@ -39,8 +39,8 @@ const PENDING_SPLIT_CAP: usize = 256;
|
||||
/// Whether low-latency mode uses the event-driven async decode loop (default) or the synchronous
|
||||
/// poll loop. Flip to `false` to A/B the two on the HUD (`design/…`); the async loop presents a
|
||||
/// decoded frame the instant it's ready instead of waiting out a poll interval. Only consulted when
|
||||
/// the user's "Low-latency mode (experimental)" toggle is ON — off, the sync loop always runs (the
|
||||
/// original pipeline).
|
||||
/// the user's "Low-latency mode" toggle is ON (now the default) — off, the sync loop always runs (the
|
||||
/// original pipeline, kept as the per-device escape hatch).
|
||||
const USE_ASYNC_DECODE: bool = true;
|
||||
|
||||
/// Per-session decode configuration, resolved by the JNI layer (`nativeStartVideo`) and passed to
|
||||
@@ -52,10 +52,10 @@ pub(crate) struct DecodeOptions {
|
||||
/// Whether Kotlin found the chosen decoder advertises `FEATURE_LowLatency` (queryable only via
|
||||
/// the Java `CodecCapabilities` API) — surfaced on the HUD next to the decoder name.
|
||||
pub ll_feature: bool,
|
||||
/// The user's "Low-latency mode (experimental)" master toggle. On ⇒ the full overhaul: async
|
||||
/// The user's "Low-latency mode" master toggle. On (default) ⇒ the full fast pipeline: async
|
||||
/// decode loop, per-SoC vendor keys, pipeline thread boosts, ADPF max-performance, forced TV
|
||||
/// mode switch. Off (default) ⇒ the original pre-overhaul pipeline, kept as the known-good
|
||||
/// baseline while the overhaul is experimental.
|
||||
/// mode switch. Off ⇒ the original synchronous pre-overhaul pipeline, kept as the per-device
|
||||
/// escape hatch.
|
||||
pub low_latency_mode: bool,
|
||||
/// TV form factor (Kotlin's `UiModeManager`): actively drive the HDMI output into the stream's
|
||||
/// refresh mode, vs. the softer seamless hint on a phone/tablet.
|
||||
@@ -409,10 +409,10 @@ fn create_codec(mime: &str, preferred: Option<&str>) -> Option<MediaCodec> {
|
||||
|
||||
/// Apply the low-latency MediaFormat keys for `codec_name`.
|
||||
///
|
||||
/// `aggressive` = the "Low-latency mode (experimental)" master toggle. **Off** (default) ⇒ the
|
||||
/// pre-overhaul key set, byte-for-byte — the standard `low-latency` key, the blind Qualcomm vendor
|
||||
/// twin, `priority = 0` AND `operating-rate = MAX` set together — kept as the known-good baseline
|
||||
/// (the profile every device streamed with before the overhaul). **On** ⇒ the Moonlight-parity
|
||||
/// `aggressive` = the "Low-latency mode" master toggle. **Off** ⇒ the pre-overhaul key set,
|
||||
/// byte-for-byte — the standard `low-latency` key, the blind Qualcomm vendor twin, `priority = 0` AND
|
||||
/// `operating-rate = MAX` set together — kept as the per-device escape hatch (the profile every device
|
||||
/// streamed with before the overhaul). **On** (default) ⇒ the Moonlight-parity
|
||||
/// profile: MediaTek's `vdec-lowlatency` (unconditionally — ignored off MediaTek), the per-SoC
|
||||
/// vendor extension keys (gated on the decoder-name prefix the way Moonlight-Android does, since a
|
||||
/// key one vendor honours is meaningless on another), and one *mutually exclusive* clock hint.
|
||||
|
||||
Reference in New Issue
Block a user