--- src/v4l2-relayd.c.orig 2026-03-30 09:59:14.804751921 -0700 +++ src/v4l2-relayd.c 2026-03-30 09:59:14.827617699 -0700 @@ -58,6 +58,7 @@ static GstElement *input_pipeline = NULL; static GstElement *output_pipeline = NULL; static GstElement *splash_pipeline = NULL; +static GTimer *input_timer = NULL; static gboolean backend_pipeline_bus_call (GstBus *bus, GstMessage *msg, @@ -278,6 +279,10 @@ { gst_element_set_state (splash_pipeline_get (), GST_STATE_NULL); gst_element_set_state (input_pipeline_get (), GST_STATE_PLAYING); + if (input_timer == NULL) + input_timer = g_timer_new (); + else + g_timer_start (input_timer); } static void @@ -285,6 +290,15 @@ { if (input_pipeline != NULL) gst_element_set_state (input_pipeline, GST_STATE_NULL); + /* After a real streaming session (>3s), reset the output pipeline + * to clear stale v4l2loopback state that causes format negotiation + * failures when switching between camera apps. Brief probes from + * PipeWire/WirePlumber (<1s) are skipped. */ + if (input_timer != NULL && g_timer_elapsed (input_timer, NULL) > 3.0 + && output_pipeline != NULL) { + gst_element_set_state (output_pipeline, GST_STATE_READY); + gst_element_set_state (output_pipeline, GST_STATE_PLAYING); + } if (input_pipeline != NULL) gst_element_set_state (splash_pipeline, GST_STATE_PLAYING); }