diff --git a/.gitignore b/.gitignore
index 0c00c4c9..e9410b6f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -38,3 +38,8 @@ CLAUDE.md
# Local flatpak-builder output (build-flatpak.sh) — ostree repo + build dir at the repo root.
.flatpak-repo/
.flatpak-build/
+
+# Nix build outputs (flake.nix) — `nix build` result symlinks + direnv cache. flake.lock IS tracked.
+/result
+/result-*
+.direnv/
diff --git a/clients/android/native/src/decode.rs b/clients/android/native/src/decode.rs
index e0715721..30a0b3d7 100644
--- a/clients/android/native/src/decode.rs
+++ b/clients/android/native/src/decode.rs
@@ -161,7 +161,7 @@ fn run_sync(
return;
}
log::info!(
- "decode: HEVC decoder started at {}x{}",
+ "decode: {mime} decoder started at {}x{}",
mode.width,
mode.height
);
@@ -815,7 +815,11 @@ fn run_async(
})),
on_error: Some(Box::new(move |e, code, _detail| {
let fatal = !code.is_recoverable() && !code.is_transient();
- log::warn!("decode: codec error {e:?} (fatal={fatal})");
+ if fatal {
+ log::error!("decode: fatal codec error — stream will stop: {e:?}");
+ } else {
+ log::warn!("decode: codec error {e:?} (recoverable)");
+ }
let _ = err_tx.send(DecodeEvent::Error { fatal });
})),
};
diff --git a/crates/pf-client-core/src/audio_wasapi.rs b/crates/pf-client-core/src/audio_wasapi.rs
index 32f7ff50..5fb1de9f 100644
--- a/crates/pf-client-core/src/audio_wasapi.rs
+++ b/crates/pf-client-core/src/audio_wasapi.rs
@@ -60,7 +60,7 @@ impl AudioPlayer {
.spawn(move || {
if let Err(e) = render_thread(pcm_rx, recycle_tx, stop_t, ready_tx, channels as u8)
{
- tracing::warn!(error = format!("{e:#}"), "audio playback thread ended");
+ tracing::warn!(error = %format!("{e:#}"), "audio playback thread ended");
}
})
.context("spawn audio thread")?;
@@ -232,7 +232,7 @@ impl MicStreamer {
.name("punktfunk-mic".into())
.spawn(move || {
if let Err(e) = mic_thread(&connector, stop_t) {
- tracing::warn!(error = format!("{e:#}"), "mic uplink thread ended");
+ tracing::warn!(error = %format!("{e:#}"), "mic uplink thread ended");
}
})
.context("spawn mic thread")?;
diff --git a/crates/pf-client-core/src/gamepad.rs b/crates/pf-client-core/src/gamepad.rs
index ab869982..a08526f3 100644
--- a/crates/pf-client-core/src/gamepad.rs
+++ b/crates/pf-client-core/src/gamepad.rs
@@ -1493,7 +1493,7 @@ impl Worker {
Err(e) => {
tracing::warn!(pad = slot.index, low, high, error = %e, "rumble: SDL set_rumble failed")
}
- Ok(()) => tracing::debug!(pad = slot.index, low, high, "rumble: rendered"),
+ Ok(()) => tracing::trace!(pad = slot.index, low, high, "rumble: rendered"),
}
slot.rumble.last = (low, high);
slot.rumble.last_at = Some(Instant::now());
diff --git a/crates/pf-client-core/src/session.rs b/crates/pf-client-core/src/session.rs
index 816412fc..b6fa23e9 100644
--- a/crates/pf-client-core/src/session.rs
+++ b/crates/pf-client-core/src/session.rs
@@ -222,7 +222,7 @@ fn pump(
preferred = punktfunk_core::quic::CODEC_PYROWAVE;
} else {
tracing::warn!(
- "PUNKTFUNK_PREFER_PYROWAVE=1 but the presenter device failed the pyrowave probe — keeping the normal codec preference"
+ "PUNKTFUNK_PREFER_PYROWAVE=1 but the presenter device failed the pyrowave probe — keeping the normal codec preference"
);
}
}
@@ -791,7 +791,7 @@ fn spawn_audio(
buf.extend_from_slice(&pcm[..n]);
player.push(buf);
}
- Err(e) => tracing::debug!(error = %e, "opus decode"),
+ Err(e) => tracing::debug!(error = %e, "opus decode failed"),
}
}
Err(PunktfunkError::NoFrame) => {}
diff --git a/crates/pf-client-core/src/video.rs b/crates/pf-client-core/src/video.rs
index 07e2cb76..260d3014 100644
--- a/crates/pf-client-core/src/video.rs
+++ b/crates/pf-client-core/src/video.rs
@@ -510,7 +510,7 @@ impl Decoder {
if choice == "vaapi" {
return Err(e.context("PUNKTFUNK_DECODER=vaapi but VAAPI failed"));
}
- tracing::info!(reason = %e, "VAAPI unavailable — software decode");
+ tracing::warn!(error = %e, "VAAPI unavailable — falling back to software decode");
}
}
}
@@ -695,8 +695,8 @@ impl Decoder {
self.backend = Backend::Software(SoftwareDecoder::new(self.codec_id)?);
self.vaapi_fails = 0;
} else {
- tracing::warn!(error = %e,
- "{which} decode error — requesting keyframe, keeping hardware decode");
+ tracing::debug!(backend = which, error = %e,
+ "decode error — requesting keyframe, keeping hardware decode");
}
Ok(None)
}
@@ -1653,7 +1653,7 @@ unsafe extern "C" fn pick_vulkan(
&mut fr,
);
if r < 0 || fr.is_null() {
- tracing::warn!("avcodec_get_hw_frames_parameters(VULKAN) failed ({r})");
+ tracing::warn!(code = r, "avcodec_get_hw_frames_parameters(VULKAN) failed");
return ffi::AVPixelFormat::AV_PIX_FMT_NONE;
}
let fc = (*fr).data as *mut ffi::AVHWFramesContext;
@@ -1665,7 +1665,7 @@ unsafe extern "C" fn pick_vulkan(
as _;
let r = ffi::av_hwframe_ctx_init(fr);
if r < 0 {
- tracing::warn!("av_hwframe_ctx_init(VULKAN) failed ({r})");
+ tracing::warn!(code = r, "av_hwframe_ctx_init(VULKAN) failed");
let mut fr = fr;
ffi::av_buffer_unref(&mut fr);
return ffi::AVPixelFormat::AV_PIX_FMT_NONE;
diff --git a/crates/pf-client-core/src/video_pyrowave.rs b/crates/pf-client-core/src/video_pyrowave.rs
index f8397981..5e926472 100644
--- a/crates/pf-client-core/src/video_pyrowave.rs
+++ b/crates/pf-client-core/src/video_pyrowave.rs
@@ -537,7 +537,8 @@ impl PyroWaveDecoder {
let fence = device.create_fence(&vk::FenceCreateInfo::default(), None)?;
tracing::info!(
- mode = %format!("{width}x{height}"),
+ width,
+ height,
"PyroWave decoder open on the presenter's device (compute iDWT, BT.709 limited)"
);
Ok(PyroWaveDecoder {
@@ -602,8 +603,8 @@ impl PyroWaveDecoder {
});
self.next = 0;
tracing::info!(
- from = %format!("{}x{}", self.width, self.height),
- to = %format!("{width}x{height}"),
+ from = %format_args!("{}x{}", self.width, self.height),
+ to = %format_args!("{width}x{height}"),
"PyroWave decoder rebuilt for mid-stream resize"
);
self.width = width;
diff --git a/crates/pf-presenter/src/dmabuf.rs b/crates/pf-presenter/src/dmabuf.rs
index b718794f..7e616459 100644
--- a/crates/pf-presenter/src/dmabuf.rs
+++ b/crates/pf-presenter/src/dmabuf.rs
@@ -101,7 +101,7 @@ pub fn import(
// EGL could leave an INVALID modifier to the driver's implied choice; explicit-
// modifier images can't — LINEAR is the only honest guess (debug-visible if wrong).
let modifier = if frame.modifier == DRM_FORMAT_MOD_INVALID {
- tracing::debug!("dmabuf carried no explicit modifier — importing as LINEAR");
+ tracing::trace!("dmabuf carried no explicit modifier — importing as LINEAR");
DRM_FORMAT_MOD_LINEAR
} else {
frame.modifier
diff --git a/crates/pf-presenter/src/run.rs b/crates/pf-presenter/src/run.rs
index cce198f4..0568374f 100644
--- a/crates/pf-presenter/src/run.rs
+++ b/crates/pf-presenter/src/run.rs
@@ -184,6 +184,11 @@ struct StreamState {
// Hardware-path health: a failure streak (or a device with no import support at
// all) demotes the decoder to software via the shared flag — once per session.
dmabuf_demoted: bool,
+ /// PyroWave present has no demote rung (nothing else decodes the codec), so a
+ /// persistent non-device-lost present failure would warn on every frame. Latch it:
+ /// warn on the first failure of a streak, then stay quiet until a present succeeds.
+ #[cfg(all(target_os = "linux", feature = "pyrowave"))]
+ pyro_present_warned: bool,
hw_fails: u32,
/// The OSD's text (multi-line; rebuilt each Stats window and on a live tier cycle).
osd_text: String,
@@ -255,6 +260,8 @@ impl StreamState {
win_start: Instant::now(),
presented: PresentedWindow::default(),
dmabuf_demoted: false,
+ #[cfg(all(target_os = "linux", feature = "pyrowave"))]
+ pyro_present_warned: false,
hw_fails: 0,
osd_text: String::new(),
last_stats: None,
@@ -542,7 +549,7 @@ fn run_inner(mut opts: SessionOpts, mut mode: ModeCtl) -> Result