Dial again when rejoining a call
Rejoining signalled correctly and then sat there with no video, because the only thing that ever dialled the SFU was a token arriving — and the server mints one when you join a session, not every time you join it. The second join brought no token, so nothing dialled, and the call was live everywhere except where you could see or hear it. Two changes, both taken from how sleek does it. The token is remembered per session id, past the end of the call, so a rejoin has something to dial with even when the server sends nothing new. And the dial is attempted from every signal that might mean the call is ready — the server agreeing we joined as well as a token arriving — because none of them is reliably the one that comes last. What makes that safe is that it was already guarded: can-dial? refuses a remote SFU with no token rather than retrying in a loop that looks like a hang, and a call already up or on its way is left alone rather than re-dialled. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
a477f70 parent: 326bb74 modified
src/frq/av.jolt +54 -10 | @@ -189,6 +189,15 @@ | ||
| 189 | 189 | ;; without depending on the client that owns the connection. |
| 190 | 190 | (defonce on-dropped (atom nil)) |
| 191 | 191 | |
| 192 | +;; SFU tokens, by session id. | |
| 193 | +;; | |
| 194 | +;; The server mints one when you join, and does not necessarily mint another | |
| 195 | +;; when you join the same call again — so a client that forgets it on the way | |
| 196 | +;; out has nothing to dial with on the way back in, and rejoining signals | |
| 197 | +;; correctly and then sits there with no video. Kept past the call for exactly | |
| 198 | +;; that, and overwritten whenever a fresh one arrives. | |
| 199 | +(defonce ^:private session-tokens (atom {})) | |
| 200 | + | |
| 192 | 201 | ;; Feeds pushed to Vidya, so the ones that stop can be dropped again. Without |
| 193 | 202 | ;; this the last frame of someone who left hangs on the wall for the rest of |
| 194 | 203 | ;; the call. |
| @@ -248,7 +257,15 @@ | ||
| 248 | 257 | ;; ended it. Leaving the local call set would leave the controls up over |
| 249 | 258 | ;; a session the SFU has already forgotten. |
| 250 | 259 | (when (and (= :ended action) (in-call? channel)) |
| 251 | - (stop-media!)))) | |
| 260 | + (stop-media!)) | |
| 261 | + ;; The server agreeing we are in the call is the other moment worth | |
| 262 | + ;; dialling on. A join we opened optimistically has no session id until | |
| 263 | + ;; this arrives — and a *re*join often brings no token with it, because the | |
| 264 | + ;; server already minted one for this session and does not mint a second. | |
| 265 | + (when (and (not= :ended action) (in-call? channel)) | |
| 266 | + (when (and (seq session-id) (str/blank? (:session-id @local-call))) | |
| 267 | + (swap! local-call assoc :session-id session-id)) | |
| 268 | + true))) | |
| 252 | 269 | |
| 253 | 270 | ;; --- the media plane, as this client uses it --------------------------------- |
| 254 | 271 | |
| @@ -284,6 +301,8 @@ | ||
| 284 | 301 | (try (announce call) (catch Exception _ nil)))) |
| 285 | 302 | (stop-media!)) |
| 286 | 303 | |
| 304 | +(declare try-start-media!) | |
| 305 | + | |
| 287 | 306 | (defn- start-media! |
| 288 | 307 | "Dial the SFU for the call we have already joined over IRC. |
| 289 | 308 | |
| @@ -305,23 +324,45 @@ | ||
| 305 | 324 | (do (swap! local-call assoc :media :failed) |
| 306 | 325 | (reset! media-error (str "no SFU for " server)))))) |
| 307 | 326 | |
| 327 | +(defn try-start-media! | |
| 328 | + "Dial the SFU if there is a call to dial for, and we are not already on it. | |
| 329 | + | |
| 330 | + Called from every signal that might mean the call is ready — the server | |
| 331 | + agreeing we joined, a token arriving, a rejoin — because none of them is | |
| 332 | + reliably the one that comes last. What makes that safe is the two guards: | |
| 333 | + `can-dial?` refuses a remote SFU with no token rather than retrying in a | |
| 334 | + loop that looks like a hang, and a call already up or on its way is left | |
| 335 | + alone rather than re-dialled." | |
| 336 | + [server] | |
| 337 | + (when-let [{:keys [session-id token media]} @local-call] | |
| 338 | + (when (and (seq session-id) | |
| 339 | + (not (contains? #{:dialling :live} media)) | |
| 340 | + (can-dial? server token)) | |
| 341 | + (start-media! server)))) | |
| 342 | + | |
| 308 | 343 | (defn apply-token! |
| 309 | - "The server minted us an SFU token — dial the media plane with it. | |
| 344 | + "The server minted us an SFU token — remember it, and dial. | |
| 310 | 345 | |
| 311 | 346 | This arrives as a TAGMSG directed at our own nick rather than at the channel, |
| 312 | 347 | so the buffer it came in on says nothing about which call it is for; the |
| 313 | - session id in the tag does. One that names a different session than ours is | |
| 314 | - not ours." | |
| 348 | + session id in the tag does. One naming a different session than ours is not | |
| 349 | + ours. | |
| 350 | + | |
| 351 | + Remembered past the end of the call, because the server does not always mint | |
| 352 | + a second one when you rejoin the same session." | |
| 315 | 353 | [server session-id token] |
| 316 | 354 | (when-let [lc @local-call] |
| 317 | 355 | (when (or (str/blank? session-id) |
| 318 | 356 | (str/blank? (:session-id lc)) |
| 319 | 357 | (= session-id (:session-id lc))) |
| 320 | - (swap! local-call | |
| 321 | - #(-> % | |
| 322 | - (assoc :token token :awaiting-start? false) | |
| 323 | - (cond-> (seq session-id) (assoc :session-id session-id)))) | |
| 324 | - (start-media! server)))) | |
| 358 | + (let [sid (if (seq session-id) session-id (:session-id lc))] | |
| 359 | + (when (seq sid) | |
| 360 | + (swap! session-tokens assoc sid token)) | |
| 361 | + (swap! local-call | |
| 362 | + #(-> % | |
| 363 | + (assoc :token token :awaiting-start? false) | |
| 364 | + (cond-> (seq sid) (assoc :session-id sid))))) | |
| 365 | + (try-start-media! server)))) | |
| 325 | 366 | |
| 326 | 367 | (defn begin! |
| 327 | 368 | "Record that this device is joining `channel`, before the server has agreed. |
| @@ -338,7 +379,10 @@ | ||
| 338 | 379 | :session-id (or session-id "") |
| 339 | 380 | :instance instance |
| 340 | 381 | :nick nick |
| 341 | - :token nil | |
| 382 | + ;; What we were given last time we were in this call, if | |
| 383 | + ;; anything. A rejoin the server answers with no new token | |
| 384 | + ;; still has something to dial with. | |
| 385 | + :token (get @session-tokens (or session-id "")) | |
| 342 | 386 | :awaiting-start? (str/blank? (or session-id "")) |
| 343 | 387 | :muted? (boolean muted?) |
| 344 | 388 | :speaker-muted? (boolean speaker-muted?) |
| @@ -189,6 +189,15 @@ | |||
| 189 | ;; without depending on the client that owns the connection. | 189 | ;; without depending on the client that owns the connection. |
| 190 | (defonce on-dropped (atom nil)) | 190 | (defonce on-dropped (atom nil)) |
| 191 | 191 | ||
| 192 | +;; SFU tokens, by session id. | ||
| 193 | +;; | ||
| 194 | +;; The server mints one when you join, and does not necessarily mint another | ||
| 195 | +;; when you join the same call again — so a client that forgets it on the way | ||
| 196 | +;; out has nothing to dial with on the way back in, and rejoining signals | ||
| 197 | +;; correctly and then sits there with no video. Kept past the call for exactly | ||
| 198 | +;; that, and overwritten whenever a fresh one arrives. | ||
| 199 | +(defonce ^:private session-tokens (atom {})) | ||
| 200 | + | ||
| 192 | ;; Feeds pushed to Vidya, so the ones that stop can be dropped again. Without | 201 | ;; Feeds pushed to Vidya, so the ones that stop can be dropped again. Without |
| 193 | ;; this the last frame of someone who left hangs on the wall for the rest of | 202 | ;; this the last frame of someone who left hangs on the wall for the rest of |
| 194 | ;; the call. | 203 | ;; the call. |
| @@ -248,7 +257,15 @@ | |||
| 248 | ;; ended it. Leaving the local call set would leave the controls up over | 257 | ;; ended it. Leaving the local call set would leave the controls up over |
| 249 | ;; a session the SFU has already forgotten. | 258 | ;; a session the SFU has already forgotten. |
| 250 | (when (and (= :ended action) (in-call? channel)) | 259 | (when (and (= :ended action) (in-call? channel)) |
| 251 | - (stop-media!)))) | 260 | + (stop-media!)) |
| 261 | + ;; The server agreeing we are in the call is the other moment worth | ||
| 262 | + ;; dialling on. A join we opened optimistically has no session id until | ||
| 263 | + ;; this arrives — and a *re*join often brings no token with it, because the | ||
| 264 | + ;; server already minted one for this session and does not mint a second. | ||
| 265 | + (when (and (not= :ended action) (in-call? channel)) | ||
| 266 | + (when (and (seq session-id) (str/blank? (:session-id @local-call))) | ||
| 267 | + (swap! local-call assoc :session-id session-id)) | ||
| 268 | + true))) | ||
| 252 | 269 | ||
| 253 | ;; --- the media plane, as this client uses it --------------------------------- | 270 | ;; --- the media plane, as this client uses it --------------------------------- |
| 254 | 271 | ||
| @@ -284,6 +301,8 @@ | |||
| 284 | (try (announce call) (catch Exception _ nil)))) | 301 | (try (announce call) (catch Exception _ nil)))) |
| 285 | (stop-media!)) | 302 | (stop-media!)) |
| 286 | 303 | ||
| 304 | +(declare try-start-media!) | ||
| 305 | + | ||
| 287 | (defn- start-media! | 306 | (defn- start-media! |
| 288 | "Dial the SFU for the call we have already joined over IRC. | 307 | "Dial the SFU for the call we have already joined over IRC. |
| 289 | 308 | ||
| @@ -305,23 +324,45 @@ | |||
| 305 | (do (swap! local-call assoc :media :failed) | 324 | (do (swap! local-call assoc :media :failed) |
| 306 | (reset! media-error (str "no SFU for " server)))))) | 325 | (reset! media-error (str "no SFU for " server)))))) |
| 307 | 326 | ||
| 327 | +(defn try-start-media! | ||
| 328 | + "Dial the SFU if there is a call to dial for, and we are not already on it. | ||
| 329 | + | ||
| 330 | + Called from every signal that might mean the call is ready — the server | ||
| 331 | + agreeing we joined, a token arriving, a rejoin — because none of them is | ||
| 332 | + reliably the one that comes last. What makes that safe is the two guards: | ||
| 333 | + `can-dial?` refuses a remote SFU with no token rather than retrying in a | ||
| 334 | + loop that looks like a hang, and a call already up or on its way is left | ||
| 335 | + alone rather than re-dialled." | ||
| 336 | + [server] | ||
| 337 | + (when-let [{:keys [session-id token media]} @local-call] | ||
| 338 | + (when (and (seq session-id) | ||
| 339 | + (not (contains? #{:dialling :live} media)) | ||
| 340 | + (can-dial? server token)) | ||
| 341 | + (start-media! server)))) | ||
| 342 | + | ||
| 308 | (defn apply-token! | 343 | (defn apply-token! |
| 309 | - "The server minted us an SFU token — dial the media plane with it. | 344 | + "The server minted us an SFU token — remember it, and dial. |
| 310 | 345 | ||
| 311 | This arrives as a TAGMSG directed at our own nick rather than at the channel, | 346 | This arrives as a TAGMSG directed at our own nick rather than at the channel, |
| 312 | so the buffer it came in on says nothing about which call it is for; the | 347 | so the buffer it came in on says nothing about which call it is for; the |
| 313 | - session id in the tag does. One that names a different session than ours is | 348 | + session id in the tag does. One naming a different session than ours is not |
| 314 | - not ours." | 349 | + ours. |
| 350 | + | ||
| 351 | + Remembered past the end of the call, because the server does not always mint | ||
| 352 | + a second one when you rejoin the same session." | ||
| 315 | [server session-id token] | 353 | [server session-id token] |
| 316 | (when-let [lc @local-call] | 354 | (when-let [lc @local-call] |
| 317 | (when (or (str/blank? session-id) | 355 | (when (or (str/blank? session-id) |
| 318 | (str/blank? (:session-id lc)) | 356 | (str/blank? (:session-id lc)) |
| 319 | (= session-id (:session-id lc))) | 357 | (= session-id (:session-id lc))) |
| 320 | - (swap! local-call | 358 | + (let [sid (if (seq session-id) session-id (:session-id lc))] |
| 321 | - #(-> % | 359 | + (when (seq sid) |
| 322 | - (assoc :token token :awaiting-start? false) | 360 | + (swap! session-tokens assoc sid token)) |
| 323 | - (cond-> (seq session-id) (assoc :session-id session-id)))) | 361 | + (swap! local-call |
| 324 | - (start-media! server)))) | 362 | + #(-> % |
| 363 | + (assoc :token token :awaiting-start? false) | ||
| 364 | + (cond-> (seq sid) (assoc :session-id sid))))) | ||
| 365 | + (try-start-media! server)))) | ||
| 325 | 366 | ||
| 326 | (defn begin! | 367 | (defn begin! |
| 327 | "Record that this device is joining `channel`, before the server has agreed. | 368 | "Record that this device is joining `channel`, before the server has agreed. |
| @@ -338,7 +379,10 @@ | |||
| 338 | :session-id (or session-id "") | 379 | :session-id (or session-id "") |
| 339 | :instance instance | 380 | :instance instance |
| 340 | :nick nick | 381 | :nick nick |
| 341 | - :token nil | 382 | + ;; What we were given last time we were in this call, if |
| 383 | + ;; anything. A rejoin the server answers with no new token | ||
| 384 | + ;; still has something to dial with. | ||
| 385 | + :token (get @session-tokens (or session-id "")) | ||
| 342 | :awaiting-start? (str/blank? (or session-id "")) | 386 | :awaiting-start? (str/blank? (or session-id "")) |
| 343 | :muted? (boolean muted?) | 387 | :muted? (boolean muted?) |
| 344 | :speaker-muted? (boolean speaker-muted?) | 388 | :speaker-muted? (boolean speaker-muted?) |
modified
src/frq/state.jolt +7 -0 | @@ -276,6 +276,13 @@ | ||
| 276 | 276 | someone joining is a number quietly changing in a banner." |
| 277 | 277 | [channel st] |
| 278 | 278 | (av/apply-state! channel st) |
| 279 | + ;; And try to dial. The token may already be in hand — from this join, or | |
| 280 | + ;; from the last time we were in this same session — in which case the | |
| 281 | + ;; server's agreement that we are in the call is the last thing we were | |
| 282 | + ;; waiting for. `try-start-media!` refuses if there is nothing to dial with | |
| 283 | + ;; or a call is already up, so calling it on every state change is safe. | |
| 284 | + (when (av/in-call? channel) | |
| 285 | + (av/try-start-media! @form-host)) | |
| 279 | 286 | (let [line (av/state-message st)] |
| 280 | 287 | (when (seq line) |
| 281 | 288 | (push-message! channel "*" line)))) |
| @@ -276,6 +276,13 @@ | |||
| 276 | someone joining is a number quietly changing in a banner." | 276 | someone joining is a number quietly changing in a banner." |
| 277 | [channel st] | 277 | [channel st] |
| 278 | (av/apply-state! channel st) | 278 | (av/apply-state! channel st) |
| 279 | + ;; And try to dial. The token may already be in hand — from this join, or | ||
| 280 | + ;; from the last time we were in this same session — in which case the | ||
| 281 | + ;; server's agreement that we are in the call is the last thing we were | ||
| 282 | + ;; waiting for. `try-start-media!` refuses if there is nothing to dial with | ||
| 283 | + ;; or a call is already up, so calling it on every state change is safe. | ||
| 284 | + (when (av/in-call? channel) | ||
| 285 | + (av/try-start-media! @form-host)) | ||
| 279 | (let [line (av/state-message st)] | 286 | (let [line (av/state-message st)] |
| 280 | (when (seq line) | 287 | (when (seq line) |
| 281 | (push-message! channel "*" line)))) | 288 | (push-message! channel "*" line)))) |