nandi/jolt-nativepublic Fork 0
600f207e2f91fc746a6545b141494a260e410f60
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.

Lift freeq's AV media plane out of sleek 90f8b89 · on 600f207e2f91fc746a6545b141494a260e410f60 · nandi · 19d ago
joltmoq.h · 169 lines · 7.0 KBC Blame HistoryRaw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
/* joltmoq — freeq's AV media plane, for callers that are not Rust.
 *
 * A freeq call has two halves. *Signaling* rides IRC TAGMSGs
 * (`+freeq.at/av-start`, `av-join`, `av-leave`, and the server's `av-state`
 * broadcasts back), and is not here: a client that speaks IRC already has
 * everything it needs for that in whatever language it speaks IRC in. *Media*
 * rides MoQ — Media over QUIC — through the freeq SFU, and is what this is.
 *
 * The boundary begins once the session id and the SFU token are known, and
 * ends when the call does.
 *
 * Rules for every function below:
 *
 *   - Only integers, doubles, borrowed UTF-8 strings and one borrowed pixel
 *     pointer cross. Nothing here retains caller memory past a call, and
 *     nothing hands out memory the caller must free.
 *   - A returned string is valid until the next call to the same function.
 *     Copy it if you need to keep it.
 *   - An empty string argument means "no preference" — the system default.
 *   - Booleans cross as 0 or 1.
 *   - Panics are caught at the boundary, so a bug in a decoder is a black tile
 *     rather than a dead process.
 *   - Nothing calls back. Everything asynchronous is polled.
 *   - One call at a time. There is one microphone, so there is one session.
 */

#ifndef JOLTMOQ_H
#define JOLTMOQ_H

#ifdef __cplusplus
extern "C" {
#endif

/* Logging to stderr, honouring RUST_LOG. Safe to call twice. Worth calling
 * first while a call refuses to connect: the media plane says a great deal
 * about why, and says none of it otherwise. */
void joltmoq_init_logging(void);

/* Lifecycle
 *
 * `joltmoq_start` joins the call at `sfu_url` (see joltmoq_sfu_url) as `nick`.
 * `session_id` is what the server broadcast in `+freeq.at/av-id`; `instance`
 * is the per-device id from your own `av-join` — two devices signed in as the
 * same person need different ones, or their broadcast paths collide and each
 * unpublishes the other.
 *
 * Returns 1 when the media task started. That is not the same as connected:
 * poll the status for that. Starting a second call while one is live is
 * refused.
 *
 * `joltmoq_stop` waits briefly for MoQ to be torn down rather than aborting.
 * An abandoned broadcast lingers on the SFU, and peers subscribe to it and
 * hear silence from someone who looks present.
 */
int joltmoq_start(const char *sfu_url, const char *session_id, const char *nick,
                  const char *instance, int muted, int speaker_muted, int camera,
                  const char *camera_id, const char *mic_id, const char *speaker_id);
void joltmoq_stop(void);
int joltmoq_is_live(void);

/* Controls
 *
 * Mic mute and speaker mute are deliberately separate: muting the speaker
 * (deafening) leaves peers hearing you if the mic is open. The camera device
 * is only held while publishing, so turning the camera off gives the hardware
 * back to the rest of the machine.
 *
 * A control with no call under it is inert, not an error — a UI that sends a
 * mute as the call is ending must not have to race it.
 *
 * `joltmoq_mic_level` is the live envelope, 0.0 to 1.0: what a level meter
 * draws. 0 when no call is up.
 */
void joltmoq_set_muted(int muted);
void joltmoq_set_speaker_muted(int muted);
void joltmoq_set_camera(int enabled);
void joltmoq_set_camera_device(const char *id);
void joltmoq_set_mic_device(const char *id);
void joltmoq_set_speaker_device(const char *id);
double joltmoq_mic_level(void);

/* Status
 *
 * The media task runs on its own threads; this is how what it learns reaches
 * yours. Drain `joltmoq_poll_status` until it answers NONE wherever you poll —
 * beside a repaint is the natural place. Missing an update means a call that is
 * up still looks like it is connecting.
 *
 * The accessors describe the most recently dequeued status. `has_camera` is
 * false when there is no capture device for this call at all, so a camera
 * control has nothing to offer; `has_mic` is false for listen-only, where
 * audio is still published, as silence.
 */
#define JOLTMOQ_STATUS_NONE 0
#define JOLTMOQ_STATUS_LIVE 1
#define JOLTMOQ_STATUS_ENDED 2
#define JOLTMOQ_STATUS_FAILED 3

int joltmoq_poll_status(void);
const char *joltmoq_status_text(void); /* why it failed; "" otherwise */
int joltmoq_status_has_camera(void);
int joltmoq_status_has_mic(void);

/* Video
 *
 * Loop `joltmoq_frame_poll` until it answers 0, reading the four accessors for
 * each frame it hands over and passing them to whatever paints. Only what
 * changed comes over: a participant sitting still costs nothing, and a decoder
 * running ahead of the window is coalesced to its newest frame rather than
 * queued behind stale ones.
 *
 * The key is the participant's nick, or "__local__" for the self-view.
 *
 * `joltmoq_frame_rgba` is width * height * 4 bytes, row-major, 8 bits a
 * channel, un-premultiplied and opaque. It is BORROWED, and only until the
 * next `joltmoq_frame_poll` — that is the point of polling rather than
 * copying: a frame is a megabyte or two, and copying it out so you can hand it
 * straight to a texture upload would be two copies a frame for nothing.
 *
 * `joltmoq_video_keys` is every feed the call currently carries, one a line.
 * Use it to notice a tile you should stop painting: someone who left stops
 * appearing, while their last frame would otherwise hang on the wall for the
 * rest of the call.
 */
int joltmoq_frame_poll(void);
const char *joltmoq_frame_key(void);
int joltmoq_frame_width(void);
int joltmoq_frame_height(void);
const unsigned char *joltmoq_frame_rgba(void);
const char *joltmoq_video_keys(void);

/* Devices
 *
 * One a line, each `id<TAB>name<TAB>default`, where the last column is 1 for
 * the system default. Tab and newline delimit because a device name may hold
 * anything else — "EMEET SmartCam C960, Mono" has spaces and a comma in it.
 *
 * Enumerating opens nothing, so it is safe before a call and during one. Empty
 * is a normal answer on a machine with no such device.
 */
const char *joltmoq_cameras(void);
const char *joltmoq_microphones(void);
const char *joltmoq_speakers(void);

/* Dialling
 *
 * `joltmoq_sfu_url` builds what `joltmoq_start` wants from whatever your
 * client knows the IRC server as — "irc.freeq.at:6697" and
 * "wss://irc.freeq.at/irc" both become "https://irc.freeq.at/av/moq" — plus
 * the JWT the server minted in `+freeq.at/av-token`. Empty string when the
 * server is not something a URL can be made of.
 *
 * `joltmoq_can_dial` answers whether the attempt is worth making. A remote SFU
 * with no token accepts the connection and closes it, and the MoQ client then
 * retries in a tight loop that looks, from the outside, exactly like a hang.
 *
 * `joltmoq_new_instance` is eight hex characters: the per-device id that keeps
 * two of your own devices from colliding on the SFU. Put it in your
 * `+freeq.at/av-instance` tag and hand the same one to `joltmoq_start`.
 */
const char *joltmoq_sfu_url(const char *server, const char *jwt, const char *instance);
int joltmoq_can_dial(const char *server, const char *jwt);
const char *joltmoq_new_instance(void);

#ifdef __cplusplus
}
#endif

#endif /* JOLTMOQ_H */