The missing half of #189. A call placed as a video call already opened a camera; a call placed as a voice call could never grow one, and the button was hidden unless the call was on a media server - so on the two shapes of Matrix call that do not use one, there was no camera at all. Added and removed rather than enabled and disabled. A disabled track keeps sending - a black rectangle at whatever frame rate the encoder feels like - so the far end sees a dark square where a face was and cannot tell that from a camera that has frozen. Removing the track ends it properly, and every other client then draws the person's name. The loopback harness below measures exactly this difference: with the old enabled=false the far end still counts one live video track after the camera is switched off, and with the track removed it counts none. One gesture for all three shapes of call - a media server, a mesh between several people, a single connection between two - because the button is one button and the person pressing it is asking one thing. Which shape this is belongs to the call, not to the control. On a mesh the camera is still opened once and lent to every leg, since five connections asking the machine for the same camera get one capture and four refusals. Renegotiations are now serialised. Turning a camera on and sharing a screen a second later are two changes to one connection, and starting the second offer while the first is unanswered leaves a peer connection neither end can recover from. A remote track that is taken away is dropped from the stream it arrived on, so a tile stops drawing the last frame it received - Chromium reports that as a mute before it reports it as an end, and on a removed track the mute is often all there is. cameraOn is asked of the call rather than assumed, in both branches: that block runs on every phase change, and a fixed answer put the button back to "off" the moment a call connected. The call UI is unchanged in shape and deliberately not Matrix-shaped - Discord's camera (#180) and screen share (#14) get the same row of buttons when they arrive. Co-Authored-By: Claude Opus 5 <[email protected]>
35 lines
1.4 KiB
Bash
Executable File
35 lines
1.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Two ends of a real call in one page, wired to each other.
|
|
#
|
|
# The claim this checks is the one that cannot be checked by reading: that a
|
|
# camera turned on in a call already running reaches the other end, and that
|
|
# turning it off takes it away rather than freezing it. That is an offer, an
|
|
# answer, a renegotiation each way and a track arriving on a stream somebody
|
|
# else owns - four things no unit test touches, and the renderer has no test
|
|
# runner of its own (see #212).
|
|
#
|
|
# Real RTCPeerConnections over loopback, with a canvas standing in for the
|
|
# camera and an oscillator for the microphone, so it needs no hardware and no
|
|
# second person. Open the printed URL and read the verdict; the page title
|
|
# carries it too.
|
|
set -euo pipefail
|
|
here="$(cd "$(dirname "$0")" && pwd)"
|
|
root="$(cd "$here/../.." && pwd)"
|
|
out="${TMPDIR:-/tmp}/moho-callcheck"
|
|
mkdir -p "$out"
|
|
|
|
"$root/node_modules/.bin/esbuild" "$here/loopback.ts" \
|
|
--bundle --format=iife --outfile="$out/harness.js" --log-level=warning
|
|
|
|
cat > "$out/index.html" <<'HTML'
|
|
<!doctype html>
|
|
<meta charset="utf-8">
|
|
<title>callcheck</title>
|
|
<body style="background:#111;color:#ddd;font:13px ui-monospace,monospace;padding:12px">
|
|
<pre id="log">starting…</pre>
|
|
<script src="harness.js"></script>
|
|
HTML
|
|
|
|
echo "serving $out on http://localhost:8791 - ctrl-c when done"
|
|
cd "$out" && exec python3 -m http.server 8791
|