Tune in faster: first frame ~1 s sooner #1
No reviewers
Labels
No labels
No milestone
No project
No assignees
5 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Grandiras/Watchplace!1
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "faster-tune-in"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Makes live streams start faster, especially in Surf.
I traced every stage of a real tune-in. The largest delays were on our side:
faststart-min-packets: first frame decoded and shown at once, motion starts on the full buffer as beforeResult: about 1.6 s to the first frame in Surf (was ~3 s, often more), and ~1.8 s on the watch page (was ~2.5 s). A 1080p60 stream still showed 0 dropped frames over 20 s. HLS (native build) still plays.
Not fixable on our side:
🤖 Generated with Claude Code
🤖 Reviewing this PR — the review will link the full session transcript.
📜 Live transcript — updating every ~5 minutes while the review runs.
🤖 Reviewing this PR — follow along in the action run; the reply will link the full session transcript.
📜 Live transcript — updating every ~5 minutes while the task runs.
✅ Verdict: approve — no blocking issues found.
The change looks sound, and I found nothing that should block merging. I read the diff only and did not build or run it.
Things to be aware of, none of them blocking:
promise.wait()inSession::start: it blocks the GTK main thread. The wait is only the few milliseconds ofcreate-offer, which webrtcbin handles on its own thread, so this is fine. If webrtcbin ever needed the main loop to answer, it would deadlock. Your trace shows it doesn't.faststart-min-packets:set_propertyon a property the element doesn't have panics. The property has been in rtpjitterbuffer for several GStreamer releases, so recent installs are fine. An older GStreamer would crash on the first tune-in. Checkingel.find_property("faststart-min-packets")first would make this safe.warm_up()on every card hover: each hover issues a new preconnect. libsoup reuses an idle connection, so the cost is small. A cheap "already warm" timestamp would avoid the repeated calls.playing_on_first_frame: it callsset_statefrom inside theinvalidate-contentssignal. That is fine as long asset_statelisteners don't trigger paintable invalidation themselves, which is worth keeping in mind.Cleanly done: the dedicated WHEP session, aborting the negotiation task via
stop_whepwhen switching streams, and removing the hand-rolled oneshot.🧠 claude-sonnet-5-5 · ⏱ 0m 14s · 2 turns · tokens: 22.9k in / 1.8k out (+0 cache-read)
📜 Full session transcript
✅ Verdict: approve — no blocking issues found.
Good change; the lifecycle handling around it holds up.
Checked in context:
stop_whep/abort ordering — abortingwhep_taskbefore taking the session is sound: the future is cancelled at its next await point,self.whepstill holds anRc<Session>so itsDrop(pipeline → NULL) runs reliably, and nothing can callwhep_failed()from a stale attempt sinceRc::ptr_eqalready guarded that.play_url/play_vod/stop_pipelinesbehavior is unchanged relative to the old inlinewhep.take().playing_on_first_frame— one connection per paintable (hls once at construction, one per WHEP session), so no signal leak across tune-ins; theState::Loading+framesguard makes repeatinvalidate_contentsfires during playback a no-op, andrestart()resetsframesso subsequent HLS tune-ins still trigger it. Old sessions can't paint while HLS isLoadingbecause their pipeline is NULLed instop_whep.faststart-min-packets = 1— verified against the GStreamer docs: the property exists (since 1.14), default 0 means faststart is disabled, and the doc text ("number of consecutive packets needed to start… the jitterbuffer will by default start after the latency has elapsed") matches the intended effect.guint→u32is right.preconnect_async— correct libsoup3 API for the job; the preconnect only establishes the connection, no HTTP exchange, so repeatedwarm_up()on hover is cheap.SESSION.with(|s| s.clone())stays on the main thread (all futures here arespawn_future_local), which is what libsoup requires.Two non-blocking notes:
promise.wait()inSession::startnow blocks the main thread with no cap, where the old oneshot bailed after 5 s. webrtcbin does resolve create-offer from its own task thread, so no deadlock, and since the main loop is blocked during the wait,stop_whepcan't race it either — just be aware the UI thread relies on that promise always replying.poll_statsalready used the samewait()pattern, so this is consistent with the codebase.whep::warm_up()at startup opens a connection to the node at launch even if the user never opens a live stream (and hover fires it before thehover_previewscheck). Intentional per the comment and cheap, but worth knowing it's a network touch on app open.Numbers and "not fixable on our side" caveats look plausible and the risk profile is low; nothing blocking.
⏱ 1m 36s · 5 steps · $0.0123 · tokens: 47.6k in / 850 out (+43.8k cache-read) · Go: 5h 0% · wk 8% · mo 95%
📜 Full session transcript
🤖 Reviewing this PR — follow along in the action run; the reply will link the full session transcript.
📜 Live transcript — updating every ~5 minutes while the task runs.
✅ Verdict: approve — no blocking issues found.
The earlier review had no blocking issues, and I found nothing new that blocks this version. I read the diff and did not build or run it.
The latest commit's change is
set_idle_timeout(300)on the dedicated WHEP session. It matches the stated goal of keeping the tune-in connection open as long as the node does.preconnect_asyncis called with a valid signature.The four non-blocking notes from my last pass still apply:
promise.wait()on the main thread: this is safe only while webrtcbin answerscreate-offeron its own thread, which your trace shows it does.faststart-min-packets:set_propertypanics if the element lacks the property, so an older GStreamer would crash on the first tune-in. A guard withel.find_property("faststart-min-packets")would make it safe.warm_up()on every card hover: each hover issues a fresh preconnect, which is cheap because libsoup reuses an idle connection. A timestamp check would avoid the repeated calls.playing_on_first_frame: it callsset_statefrom insideinvalidate-contents, which is fine as long asset_statelisteners never trigger a paintable invalidation themselves.The dedicated session, the abort of the negotiation task in
stop_whep, and the removal of the hand-rolled oneshot are all clean.🧠 claude-sonnet-5-5 · ⏱ 0m 9s · 2 turns · tokens: 10.7k in / 1.3k out (+13.0k cache-read)
📜 Full session transcript
🤖 Reviewing this PR — the review will link the full session transcript.
📜 Live transcript — updating every ~5 minutes while the review runs.
✅ Verdict: approve — no blocking issues found.
Re-review: the only change since the last pass is
set_idle_timeout(300)on the dedicated WHEP session (src/whep.rs:33). It is a valid libsoup3 property (seconds, guint) and matches the comment's rationale that the node keeps idle connections for minutes, closing the loop I noted earlier about keeping the preconnected connection alive. Everything else is as reviewed and approved before:stop_whepaborts the negotiation task whileRc<Session>Drop reliably NULLs the pipeline;playing_on_first_frameconnects once per paintable and is guarded byState::Loading+frames;faststart-min-packetsis a real property (since 1.14);preconnect_asyncusage is correct.Remaining non-blocking notes (also on the thread already):
promise.wait()on the main thread is safe only while webrtcbin resolvescreate-offeron its own thread;set_property("faststart-min-packets")would panic on an older GStreamer without the property (afind_propertyguard makes it safe); hover-triggeredwarm_up()could be timestamp-gated; callingset_statefrom insideinvalidate-contentsrelies on no listener invalidating the paintable in turn. Nothing blocks merging.⏱ 0m 14s · 3 steps · $0.0044 · tokens: 22.6k in / 588 out (+13.4k cache-read) · Go: 5h 0% · wk 8% · mo 95%
📜 Full session transcript