Run without a media plane
`libjoltmoq` is not on the phone — nobody has cross-built the media plane for Android — and `-main` called into it before the window was up, so the whole client died at startup with "no entry for joltmoq_init_logging" and took the event loop with it. A client that cannot make calls is still a client. Whether the library is there at all is asked once, by calling the smallest thing in it, and that answer gates the call surface: no pump, no Call button, and no dial from an incoming call. Everything else — the channels, the pictures, the sign-in — does not care. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ddbf5e1 parent: 556ab58 modified
src/frq/app.jolt +1 -0 | @@ -718,6 +718,7 @@ | ||
| 718 | 718 | [:vbox {:key :call} |
| 719 | 719 | (when (and name |
| 720 | 720 | (str/starts-with? name "#") |
| 721 | + (av/available?) | |
| 721 | 722 | (not (av/call-in name)) |
| 722 | 723 | (not (av/in-call?))) |
| 723 | 724 | [:button {:label "Call" :on-click #(s/start-call! name)}])]] |
| @@ -718,6 +718,7 @@ | |||
| 718 | [:vbox {:key :call} | 718 | [:vbox {:key :call} |
| 719 | (when (and name | 719 | (when (and name |
| 720 | (str/starts-with? name "#") | 720 | (str/starts-with? name "#") |
| 721 | + (av/available?) | ||
| 721 | (not (av/call-in name)) | 722 | (not (av/call-in name)) |
| 722 | (not (av/in-call?))) | 723 | (not (av/in-call?))) |
| 723 | [:button {:label "Call" :on-click #(s/start-call! name)}])]] | 724 | [:button {:label "Call" :on-click #(s/start-call! name)}])]] |
modified
src/frq/av.jolt +25 -5 | @@ -73,6 +73,21 @@ | ||
| 73 | 73 | (def ^:private status-ended 2) |
| 74 | 74 | (def ^:private status-failed 3) |
| 75 | 75 | |
| 76 | +(defonce ^:private media-plane | |
| 77 | + ;; Whether `libjoltmoq` is here at all, asked once by calling the smallest | |
| 78 | + ;; thing in it. It is not on the phone — nobody has cross-built the media | |
| 79 | + ;; plane for Android — and a client that cannot make calls is still a client, | |
| 80 | + ;; so the answer gates the call surface rather than ending the run. Every | |
| 81 | + ;; other binding in this namespace is reached only from behind it. | |
| 82 | + (delay | |
| 83 | + (try (raw-init-logging) true | |
| 84 | + (catch Exception _ false)))) | |
| 85 | + | |
| 86 | +(defn available? | |
| 87 | + "Whether calls can happen here at all." | |
| 88 | + [] | |
| 89 | + @media-plane) | |
| 90 | + | |
| 76 | 91 | (defn- flag [b] (if b 1 0)) |
| 77 | 92 | (defn- pref [s] (or s "")) |
| 78 | 93 | |
| @@ -335,7 +350,8 @@ | ||
| 335 | 350 | alone rather than re-dialled." |
| 336 | 351 | [server] |
| 337 | 352 | (when-let [{:keys [session-id token media]} @local-call] |
| 338 | - (when (and (seq session-id) | |
| 353 | + (when (and (available?) | |
| 354 | + (seq session-id) | |
| 339 | 355 | (not (contains? #{:dialling :live} media)) |
| 340 | 356 | (can-dial? server token)) |
| 341 | 357 | (start-media! server)))) |
| @@ -541,17 +557,21 @@ | ||
| 541 | 557 | Worth doing unconditionally: it says nothing at all without a `RUST_LOG`, |
| 542 | 558 | and when a call misbehaves it is the only thing that knows why — MoQ |
| 543 | 559 | subscription, codec negotiation and device open all happen on the far side of |
| 544 | - the boundary, where no jolt-level trace can see them." | |
| 560 | + the boundary, where no jolt-level trace can see them. | |
| 561 | + | |
| 562 | + Asking whether the media plane is there is the same call, so this is it." | |
| 545 | 563 | [] |
| 546 | - (raw-init-logging)) | |
| 564 | + (available?)) | |
| 547 | 565 | |
| 548 | 566 | (defn install-pump! |
| 549 | - "Start pumping the media plane every frame. Returns a timer id. | |
| 567 | + "Start pumping the media plane every frame. Returns a timer id, or nil where | |
| 568 | + there is no media plane to pump. | |
| 550 | 569 | |
| 551 | 570 | Sixteen milliseconds rather than a longer gap because this is where video |
| 552 | 571 | arrives: polling slower than the window paints would show every other frame." |
| 553 | 572 | [] |
| 554 | - (vidya/every! 16 pump!)) | |
| 573 | + (when (available?) | |
| 574 | + (vidya/every! 16 pump!))) | |
| 555 | 575 | |
| 556 | 576 | (defn tiles |
| 557 | 577 | "Everyone with a picture in the current call, the self-view last. |
| @@ -73,6 +73,21 @@ | |||
| 73 | (def ^:private status-ended 2) | 73 | (def ^:private status-ended 2) |
| 74 | (def ^:private status-failed 3) | 74 | (def ^:private status-failed 3) |
| 75 | 75 | ||
| 76 | +(defonce ^:private media-plane | ||
| 77 | + ;; Whether `libjoltmoq` is here at all, asked once by calling the smallest | ||
| 78 | + ;; thing in it. It is not on the phone — nobody has cross-built the media | ||
| 79 | + ;; plane for Android — and a client that cannot make calls is still a client, | ||
| 80 | + ;; so the answer gates the call surface rather than ending the run. Every | ||
| 81 | + ;; other binding in this namespace is reached only from behind it. | ||
| 82 | + (delay | ||
| 83 | + (try (raw-init-logging) true | ||
| 84 | + (catch Exception _ false)))) | ||
| 85 | + | ||
| 86 | +(defn available? | ||
| 87 | + "Whether calls can happen here at all." | ||
| 88 | + [] | ||
| 89 | + @media-plane) | ||
| 90 | + | ||
| 76 | (defn- flag [b] (if b 1 0)) | 91 | (defn- flag [b] (if b 1 0)) |
| 77 | (defn- pref [s] (or s "")) | 92 | (defn- pref [s] (or s "")) |
| 78 | 93 | ||
| @@ -335,7 +350,8 @@ | |||
| 335 | alone rather than re-dialled." | 350 | alone rather than re-dialled." |
| 336 | [server] | 351 | [server] |
| 337 | (when-let [{:keys [session-id token media]} @local-call] | 352 | (when-let [{:keys [session-id token media]} @local-call] |
| 338 | - (when (and (seq session-id) | 353 | + (when (and (available?) |
| 354 | + (seq session-id) | ||
| 339 | (not (contains? #{:dialling :live} media)) | 355 | (not (contains? #{:dialling :live} media)) |
| 340 | (can-dial? server token)) | 356 | (can-dial? server token)) |
| 341 | (start-media! server)))) | 357 | (start-media! server)))) |
| @@ -541,17 +557,21 @@ | |||
| 541 | Worth doing unconditionally: it says nothing at all without a `RUST_LOG`, | 557 | Worth doing unconditionally: it says nothing at all without a `RUST_LOG`, |
| 542 | and when a call misbehaves it is the only thing that knows why — MoQ | 558 | and when a call misbehaves it is the only thing that knows why — MoQ |
| 543 | subscription, codec negotiation and device open all happen on the far side of | 559 | subscription, codec negotiation and device open all happen on the far side of |
| 544 | - the boundary, where no jolt-level trace can see them." | 560 | + the boundary, where no jolt-level trace can see them. |
| 561 | + | ||
| 562 | + Asking whether the media plane is there is the same call, so this is it." | ||
| 545 | [] | 563 | [] |
| 546 | - (raw-init-logging)) | 564 | + (available?)) |
| 547 | 565 | ||
| 548 | (defn install-pump! | 566 | (defn install-pump! |
| 549 | - "Start pumping the media plane every frame. Returns a timer id. | 567 | + "Start pumping the media plane every frame. Returns a timer id, or nil where |
| 568 | + there is no media plane to pump. | ||
| 550 | 569 | ||
| 551 | Sixteen milliseconds rather than a longer gap because this is where video | 570 | Sixteen milliseconds rather than a longer gap because this is where video |
| 552 | arrives: polling slower than the window paints would show every other frame." | 571 | arrives: polling slower than the window paints would show every other frame." |
| 553 | [] | 572 | [] |
| 554 | - (vidya/every! 16 pump!)) | 573 | + (when (available?) |
| 574 | + (vidya/every! 16 pump!))) | ||
| 555 | 575 | ||
| 556 | (defn tiles | 576 | (defn tiles |
| 557 | "Everyone with a picture in the current call, the self-view last. | 577 | "Everyone with a picture in the current call, the self-view last. |