nandi/jolt-nativepublic Fork 0
8cf1b3347ef6fa83297b6866dfd47d2728d4e339
Commits
Clone
git clone https://git.rickub.com/nandi/jolt-native.git
git clone ssh://git@rickub.com/nandi/jolt-native.git

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

joltmoq.h · 169 lines · 7.0 KBC Blame HistoryRaw
Lift freeq's AV media plane out of sleek 90f8b89 nandi 19d ago1/* joltmoq — freeq's AV media plane, for callers that are not Rust.
2 *
3 * A freeq call has two halves. *Signaling* rides IRC TAGMSGs
4 * (`+freeq.at/av-start`, `av-join`, `av-leave`, and the server's `av-state`
5 * broadcasts back), and is not here: a client that speaks IRC already has
6 * everything it needs for that in whatever language it speaks IRC in. *Media*
7 * rides MoQ — Media over QUIC — through the freeq SFU, and is what this is.
8 *
9 * The boundary begins once the session id and the SFU token are known, and
10 * ends when the call does.
11 *
12 * Rules for every function below:
13 *
14 * - Only integers, doubles, borrowed UTF-8 strings and one borrowed pixel
15 * pointer cross. Nothing here retains caller memory past a call, and
16 * nothing hands out memory the caller must free.
17 * - A returned string is valid until the next call to the same function.
18 * Copy it if you need to keep it.
19 * - An empty string argument means "no preference" — the system default.
20 * - Booleans cross as 0 or 1.
21 * - Panics are caught at the boundary, so a bug in a decoder is a black tile
22 * rather than a dead process.
23 * - Nothing calls back. Everything asynchronous is polled.
24 * - One call at a time. There is one microphone, so there is one session.
25 */
26
27#ifndef JOLTMOQ_H
28#define JOLTMOQ_H
29
30#ifdef __cplusplus
31extern "C" {
32#endif
33
34/* Logging to stderr, honouring RUST_LOG. Safe to call twice. Worth calling
35 * first while a call refuses to connect: the media plane says a great deal
36 * about why, and says none of it otherwise. */
37void joltmoq_init_logging(void);
38
39/* Lifecycle
40 *
41 * `joltmoq_start` joins the call at `sfu_url` (see joltmoq_sfu_url) as `nick`.
42 * `session_id` is what the server broadcast in `+freeq.at/av-id`; `instance`
43 * is the per-device id from your own `av-join` — two devices signed in as the
44 * same person need different ones, or their broadcast paths collide and each
45 * unpublishes the other.
46 *
47 * Returns 1 when the media task started. That is not the same as connected:
48 * poll the status for that. Starting a second call while one is live is
49 * refused.
50 *
51 * `joltmoq_stop` waits briefly for MoQ to be torn down rather than aborting.
52 * An abandoned broadcast lingers on the SFU, and peers subscribe to it and
53 * hear silence from someone who looks present.
54 */
55int joltmoq_start(const char *sfu_url, const char *session_id, const char *nick,
56 const char *instance, int muted, int speaker_muted, int camera,
57 const char *camera_id, const char *mic_id, const char *speaker_id);
58void joltmoq_stop(void);
59int joltmoq_is_live(void);
60
61/* Controls
62 *
63 * Mic mute and speaker mute are deliberately separate: muting the speaker
64 * (deafening) leaves peers hearing you if the mic is open. The camera device
65 * is only held while publishing, so turning the camera off gives the hardware
66 * back to the rest of the machine.
67 *
68 * A control with no call under it is inert, not an error — a UI that sends a
69 * mute as the call is ending must not have to race it.
70 *
71 * `joltmoq_mic_level` is the live envelope, 0.0 to 1.0: what a level meter
72 * draws. 0 when no call is up.
73 */
74void joltmoq_set_muted(int muted);
75void joltmoq_set_speaker_muted(int muted);
76void joltmoq_set_camera(int enabled);
77void joltmoq_set_camera_device(const char *id);
78void joltmoq_set_mic_device(const char *id);
79void joltmoq_set_speaker_device(const char *id);
80double joltmoq_mic_level(void);
81
82/* Status
83 *
84 * The media task runs on its own threads; this is how what it learns reaches
85 * yours. Drain `joltmoq_poll_status` until it answers NONE wherever you poll —
86 * beside a repaint is the natural place. Missing an update means a call that is
87 * up still looks like it is connecting.
88 *
89 * The accessors describe the most recently dequeued status. `has_camera` is
90 * false when there is no capture device for this call at all, so a camera
91 * control has nothing to offer; `has_mic` is false for listen-only, where
92 * audio is still published, as silence.
93 */
94#define JOLTMOQ_STATUS_NONE 0
95#define JOLTMOQ_STATUS_LIVE 1
96#define JOLTMOQ_STATUS_ENDED 2
97#define JOLTMOQ_STATUS_FAILED 3
98
99int joltmoq_poll_status(void);
100const char *joltmoq_status_text(void); /* why it failed; "" otherwise */
101int joltmoq_status_has_camera(void);
102int joltmoq_status_has_mic(void);
103
104/* Video
105 *
106 * Loop `joltmoq_frame_poll` until it answers 0, reading the four accessors for
107 * each frame it hands over and passing them to whatever paints. Only what
108 * changed comes over: a participant sitting still costs nothing, and a decoder
109 * running ahead of the window is coalesced to its newest frame rather than
110 * queued behind stale ones.
111 *
112 * The key is the participant's nick, or "__local__" for the self-view.
113 *
114 * `joltmoq_frame_rgba` is width * height * 4 bytes, row-major, 8 bits a
115 * channel, un-premultiplied and opaque. It is BORROWED, and only until the
116 * next `joltmoq_frame_poll` — that is the point of polling rather than
117 * copying: a frame is a megabyte or two, and copying it out so you can hand it
118 * straight to a texture upload would be two copies a frame for nothing.
119 *
120 * `joltmoq_video_keys` is every feed the call currently carries, one a line.
121 * Use it to notice a tile you should stop painting: someone who left stops
122 * appearing, while their last frame would otherwise hang on the wall for the
123 * rest of the call.
124 */
125int joltmoq_frame_poll(void);
126const char *joltmoq_frame_key(void);
127int joltmoq_frame_width(void);
128int joltmoq_frame_height(void);
129const unsigned char *joltmoq_frame_rgba(void);
130const char *joltmoq_video_keys(void);
131
132/* Devices
133 *
134 * One a line, each `id<TAB>name<TAB>default`, where the last column is 1 for
135 * the system default. Tab and newline delimit because a device name may hold
136 * anything else — "EMEET SmartCam C960, Mono" has spaces and a comma in it.
137 *
138 * Enumerating opens nothing, so it is safe before a call and during one. Empty
139 * is a normal answer on a machine with no such device.
140 */
141const char *joltmoq_cameras(void);
142const char *joltmoq_microphones(void);
143const char *joltmoq_speakers(void);
144
145/* Dialling
146 *
147 * `joltmoq_sfu_url` builds what `joltmoq_start` wants from whatever your
148 * client knows the IRC server as — "irc.freeq.at:6697" and
149 * "wss://irc.freeq.at/irc" both become "https://irc.freeq.at/av/moq" — plus
150 * the JWT the server minted in `+freeq.at/av-token`. Empty string when the
151 * server is not something a URL can be made of.
152 *
153 * `joltmoq_can_dial` answers whether the attempt is worth making. A remote SFU
154 * with no token accepts the connection and closes it, and the MoQ client then
155 * retries in a tight loop that looks, from the outside, exactly like a hang.
156 *
157 * `joltmoq_new_instance` is eight hex characters: the per-device id that keeps
158 * two of your own devices from colliding on the SFU. Put it in your
159 * `+freeq.at/av-instance` tag and hand the same one to `joltmoq_start`.
160 */
161const char *joltmoq_sfu_url(const char *server, const char *jwt, const char *instance);
162int joltmoq_can_dial(const char *server, const char *jwt);
163const char *joltmoq_new_instance(void);
164
165#ifdef __cplusplus
166}
167#endif
168
169#endif /* JOLTMOQ_H */