nandi/frqpublic Fork 0
ea3c833
Commits
Clone
git clone https://git.rickub.com/nandi/frq.git
git clone ssh://git@rickub.com/nandi/frq.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Show the video that was already arriving

The call wall asked the media plane who had a picture, at render time.
glimmer re-renders a component when a ratom it read has changed, and that
question read none — so the wall rendered once, with nobody on screen, and
was never told that anyone had appeared. Frames arrived, were handed to
Vidya, and painted into a node that had never been created.

So the pump publishes the feed list into a cell and the wall reads that.
Written only when the set actually changes: writing every frame would
rebuild every :image node sixty times a second around textures that were
fine where they were.

The row is always present now, so a tile appearing adds a child instead of
changing the shape of the tree around it — and its children are a seq
rather than a vector, because children splice and a vector is read as one
more element, which an empty one is not.

Also call joltmoq_init_logging, which was bound and never called. RUST_LOG
is the only thing that can see MoQ subscription and device open; the plane
was silent about all of it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nandi committed 2026-08-30T16:34:43-07:00 Browse files
ea3c833 parent: cd7e9d2
modified src/frq/app.jolt +14 -6
@@ -536,13 +536,20 @@
536536 "Everyone with a camera on, or nothing at all.
537537
538538 A call with no video is the normal case and should look like one a row of
539- empty frames would suggest something had failed to load."
539+ empty frames would suggest something had failed to load. The row itself is
540+ always here even when empty, so a tile appearing adds a child rather than
541+ changing the shape of the tree around it.
542+
543+ `av/tiles` reads a cell the pump writes, which is what subscribes this
544+ component: without that this would render once, with nobody on screen, and
545+ never hear that someone had turned a camera on."
540546 []
541- (let [tiles (av/tiles)]
542- [:vbox {:key :call-wall :spacing 6}
543- (when (seq tiles)
544- [:hbox {:spacing 8}
545- (mapv call-tile tiles)])]))
547+ [:vbox {:key :call-wall :spacing 6}
548+ [:hbox {:key :tiles :spacing 8}
549+ ;; A seq, not a vector: children splice, and a vector would be read as one
550+ ;; more hiccup element which an empty one is not.
551+ (for [key (av/tiles)]
552+ [call-tile key])]])
546553
547554 (defn- call-controls
548555 "What the person in a call can do about it.
@@ -787,5 +794,6 @@
787794 ;; read when it is not. It has to be a timer: `frame-rgba!` and everything
788795 ;; else that touches a node belongs to the loop thread, and this is glimmer's
789796 ;; way of getting onto it.
797+ (av/init-logging!)
790798 (vidya/after! 0 av/install-pump!)
791799 (ui/run app :title "frq" :width 520 :height 860))
@@ -536,13 +536,20 @@
536 "Everyone with a camera on, or nothing at all.536 "Everyone with a camera on, or nothing at all.
537 537
538 A call with no video is the normal case and should look like one a row of538 A call with no video is the normal case and should look like one a row of
539- empty frames would suggest something had failed to load."539+ empty frames would suggest something had failed to load. The row itself is
540+ always here even when empty, so a tile appearing adds a child rather than
541+ changing the shape of the tree around it.
542+
543+ `av/tiles` reads a cell the pump writes, which is what subscribes this
544+ component: without that this would render once, with nobody on screen, and
545+ never hear that someone had turned a camera on."
540 []546 []
541- (let [tiles (av/tiles)]547+ [:vbox {:key :call-wall :spacing 6}
542- [:vbox {:key :call-wall :spacing 6}548+ [:hbox {:key :tiles :spacing 8}
543- (when (seq tiles)549+ ;; A seq, not a vector: children splice, and a vector would be read as one
544- [:hbox {:spacing 8}550+ ;; more hiccup element which an empty one is not.
545- (mapv call-tile tiles)])]))551+ (for [key (av/tiles)]
552+ [call-tile key])]])
546 553
547 (defn- call-controls554 (defn- call-controls
548 "What the person in a call can do about it.555 "What the person in a call can do about it.
@@ -787,5 +794,6 @@
787 ;; read when it is not. It has to be a timer: `frame-rgba!` and everything794 ;; read when it is not. It has to be a timer: `frame-rgba!` and everything
788 ;; else that touches a node belongs to the loop thread, and this is glimmer's795 ;; else that touches a node belongs to the loop thread, and this is glimmer's
789 ;; way of getting onto it.796 ;; way of getting onto it.
797+ (av/init-logging!)
790 (vidya/after! 0 av/install-pump!)798 (vidya/after! 0 av/install-pump!)
791 (ui/run app :title "frq" :width 520 :height 860))799 (ui/run app :title "frq" :width 520 :height 860))
modified src/frq/av.jolt +50 -14
@@ -183,6 +183,15 @@
183183 ;; the call.
184184 (defonce ^:private painted-feeds (atom #{}))
185185
186+;; Who has a picture, in the order they should be drawn.
187+;;
188+;; A cell rather than a question asked at render time, and that is the whole
189+;; point: glimmer re-renders a component when a ratom it read has changed, so a
190+;; view that asked the media plane directly would render once with nobody on
191+;; screen and never be told that someone had appeared. The frames would arrive,
192+;; be handed to Vidya, and paint into no node at all.
193+(defonce feeds (atom []))
194+
186195 ;; The self-view is keyed this way by the media plane; the UI wants to know
187196 ;; which tile is its own, to label it and to mirror nothing else.
188197 (def local-feed "__local__")
@@ -229,7 +238,8 @@
229238 "Stop painting every feed we have been pushing."
230239 []
231240 (doseq [k @painted-feeds] (vidya/frame-drop! k))
232- (reset! painted-feeds #{}))
241+ (reset! painted-feeds #{})
242+ (reset! feeds []))
233243
234244 (defn stop-media!
235245 "Leave the media plane and forget the call. Signaling is the caller's to send
@@ -238,6 +248,7 @@
238248 []
239249 (raw-stop)
240250 (drop-feeds!)
251+ (reset! feeds [])
241252 (reset! local-call nil)
242253 (reset! media-error nil))
243254
@@ -383,14 +394,32 @@
383394 (swap! painted-feeds conj key)))
384395 (recur))))
385396
386-(defn- forget-gone-feeds!
387- "Drop the tiles of everyone who has stopped publishing."
397+(defn- order-feeds
398+ "Everyone with a picture, the self-view last.
399+
400+ Last because it is the one whose subject the person can already see, so it
401+ belongs where it will not push a face they are talking to off the row."
402+ [keys]
403+ (let [mine (filter #(= local-feed %) keys)
404+ others (sort (remove #(= local-feed %) keys))]
405+ (vec (concat others mine))))
406+
407+(defn- pump-feeds!
408+ "Reconcile who has a picture: drop the tiles of anyone who has stopped, and
409+ publish the list for the view to render from.
410+
411+ The cell is only written when the set has actually changed. Writing it every
412+ frame would re-render the call wall sixty times a second to say the same
413+ thing, and every `:image` node under it would be rebuilt around a texture
414+ that was fine where it was."
388415 []
389- (let [live (set (remove str/blank? (str/split-lines (or (video-keys) ""))))
390- gone (remove live @painted-feeds)]
391- (doseq [k gone]
416+ (let [live (set (remove str/blank? (str/split-lines (or (video-keys) ""))))]
417+ (doseq [k (remove live @painted-feeds)]
392418 (vidya/frame-drop! k)
393- (swap! painted-feeds disj k))))
419+ (swap! painted-feeds disj k))
420+ (let [ordered (order-feeds live)]
421+ (when-not (= ordered @feeds)
422+ (reset! feeds ordered)))))
394423
395424 (defn pump!
396425 "One frame's worth of the media plane. Cheap when no call is up.
@@ -401,7 +430,17 @@
401430 (when (live?)
402431 (pump-status!)
403432 (pump-frames!)
404- (forget-gone-feeds!)))
433+ (pump-feeds!)))
434+
435+(defn init-logging!
436+ "Let the media plane talk to stderr, honouring `RUST_LOG`.
437+
438+ Worth doing unconditionally: it says nothing at all without a `RUST_LOG`,
439+ and when a call misbehaves it is the only thing that knows why — MoQ
440+ subscription, codec negotiation and device open all happen on the far side of
441+ the boundary, where no jolt-level trace can see them."
442+ []
443+ (raw-init-logging))
405444
406445 (defn install-pump!
407446 "Start pumping the media plane every frame. Returns a timer id.
@@ -414,10 +453,7 @@
414453 (defn tiles
415454 "Everyone with a picture in the current call, the self-view last.
416455
417- Last because it is the one the person can already see the subject of, so it
418- belongs where it will not push a face they are talking to off the row."
456+ Reads the cell the pump writes, so a component calling this re-renders when
457+ someone turns their camera on."
419458 []
420- (let [ks (remove str/blank? (str/split-lines (or (video-keys) "")))
421- mine (filter #(= local-feed %) ks)
422- others (sort (remove #(= local-feed %) ks))]
423- (vec (concat others mine))))
459+ @feeds)
@@ -183,6 +183,15 @@
183 ;; the call.183 ;; the call.
184 (defonce ^:private painted-feeds (atom #{}))184 (defonce ^:private painted-feeds (atom #{}))
185 185
186+;; Who has a picture, in the order they should be drawn.
187+;;
188+;; A cell rather than a question asked at render time, and that is the whole
189+;; point: glimmer re-renders a component when a ratom it read has changed, so a
190+;; view that asked the media plane directly would render once with nobody on
191+;; screen and never be told that someone had appeared. The frames would arrive,
192+;; be handed to Vidya, and paint into no node at all.
193+(defonce feeds (atom []))
194+
186 ;; The self-view is keyed this way by the media plane; the UI wants to know195 ;; The self-view is keyed this way by the media plane; the UI wants to know
187 ;; which tile is its own, to label it and to mirror nothing else.196 ;; which tile is its own, to label it and to mirror nothing else.
188 (def local-feed "__local__")197 (def local-feed "__local__")
@@ -229,7 +238,8 @@
229 "Stop painting every feed we have been pushing."238 "Stop painting every feed we have been pushing."
230 []239 []
231 (doseq [k @painted-feeds] (vidya/frame-drop! k))240 (doseq [k @painted-feeds] (vidya/frame-drop! k))
232- (reset! painted-feeds #{}))241+ (reset! painted-feeds #{})
242+ (reset! feeds []))
233 243
234 (defn stop-media!244 (defn stop-media!
235 "Leave the media plane and forget the call. Signaling is the caller's to send245 "Leave the media plane and forget the call. Signaling is the caller's to send
@@ -238,6 +248,7 @@
238 []248 []
239 (raw-stop)249 (raw-stop)
240 (drop-feeds!)250 (drop-feeds!)
251+ (reset! feeds [])
241 (reset! local-call nil)252 (reset! local-call nil)
242 (reset! media-error nil))253 (reset! media-error nil))
243 254
@@ -383,14 +394,32 @@
383 (swap! painted-feeds conj key)))394 (swap! painted-feeds conj key)))
384 (recur))))395 (recur))))
385 396
386-(defn- forget-gone-feeds!397+(defn- order-feeds
387- "Drop the tiles of everyone who has stopped publishing."398+ "Everyone with a picture, the self-view last.
399+
400+ Last because it is the one whose subject the person can already see, so it
401+ belongs where it will not push a face they are talking to off the row."
402+ [keys]
403+ (let [mine (filter #(= local-feed %) keys)
404+ others (sort (remove #(= local-feed %) keys))]
405+ (vec (concat others mine))))
406+
407+(defn- pump-feeds!
408+ "Reconcile who has a picture: drop the tiles of anyone who has stopped, and
409+ publish the list for the view to render from.
410+
411+ The cell is only written when the set has actually changed. Writing it every
412+ frame would re-render the call wall sixty times a second to say the same
413+ thing, and every `:image` node under it would be rebuilt around a texture
414+ that was fine where it was."
388 []415 []
389- (let [live (set (remove str/blank? (str/split-lines (or (video-keys) ""))))416+ (let [live (set (remove str/blank? (str/split-lines (or (video-keys) ""))))]
390- gone (remove live @painted-feeds)]417+ (doseq [k (remove live @painted-feeds)]
391- (doseq [k gone]
392 (vidya/frame-drop! k)418 (vidya/frame-drop! k)
393- (swap! painted-feeds disj k))))419+ (swap! painted-feeds disj k))
420+ (let [ordered (order-feeds live)]
421+ (when-not (= ordered @feeds)
422+ (reset! feeds ordered)))))
394 423
395 (defn pump!424 (defn pump!
396 "One frame's worth of the media plane. Cheap when no call is up.425 "One frame's worth of the media plane. Cheap when no call is up.
@@ -401,7 +430,17 @@
401 (when (live?)430 (when (live?)
402 (pump-status!)431 (pump-status!)
403 (pump-frames!)432 (pump-frames!)
404- (forget-gone-feeds!)))433+ (pump-feeds!)))
434+
435+(defn init-logging!
436+ "Let the media plane talk to stderr, honouring `RUST_LOG`.
437+
438+ Worth doing unconditionally: it says nothing at all without a `RUST_LOG`,
439+ and when a call misbehaves it is the only thing that knows why — MoQ
440+ subscription, codec negotiation and device open all happen on the far side of
441+ the boundary, where no jolt-level trace can see them."
442+ []
443+ (raw-init-logging))
405 444
406 (defn install-pump!445 (defn install-pump!
407 "Start pumping the media plane every frame. Returns a timer id.446 "Start pumping the media plane every frame. Returns a timer id.
@@ -414,10 +453,7 @@
414 (defn tiles453 (defn tiles
415 "Everyone with a picture in the current call, the self-view last.454 "Everyone with a picture in the current call, the self-view last.
416 455
417- Last because it is the one the person can already see the subject of, so it456+ Reads the cell the pump writes, so a component calling this re-renders when
418- belongs where it will not push a face they are talking to off the row."457+ someone turns their camera on."
419 []458 []
420- (let [ks (remove str/blank? (str/split-lines (or (video-keys) "")))459+ @feeds)
421- mine (filter #(= local-feed %) ks)
422- others (sort (remove #(= local-feed %) ks))]
423- (vec (concat others mine))))