moq/api.rs
1use crate::{Error, State, ffi};
2
3use std::ffi::c_char;
4use std::ffi::c_void;
5use std::str::FromStr;
6
7use tracing::Level;
8
9/// Information about a video rendition in the catalog.
10#[repr(C)]
11#[allow(non_camel_case_types)]
12pub struct moq_video_config {
13 /// The name of the track, NOT NULL terminated.
14 pub name: *const c_char,
15 pub name_len: usize,
16
17 /// The codec of the track, NOT NULL terminated
18 pub codec: *const c_char,
19 pub codec_len: usize,
20
21 /// The description of the track, or NULL if not used.
22 /// This is codec specific, for example H264:
23 /// - NULL: annex.b encoded
24 /// - Non-NULL: AVCC encoded
25 pub description: *const u8,
26 pub description_len: usize,
27
28 /// The encoded width/height of the media, or NULL if not available
29 pub coded_width: *const u32,
30 pub coded_height: *const u32,
31}
32
33/// Information about an audio rendition in the catalog.
34#[repr(C)]
35#[allow(non_camel_case_types)]
36pub struct moq_audio_config {
37 /// The name of the track, NOT NULL terminated
38 pub name: *const c_char,
39 pub name_len: usize,
40
41 /// The codec of the track, NOT NULL terminated
42 pub codec: *const c_char,
43 pub codec_len: usize,
44
45 /// The description of the track, or NULL if not used.
46 pub description: *const u8,
47 pub description_len: usize,
48
49 /// The sample rate of the track in Hz
50 pub sample_rate: u32,
51
52 /// The number of channels in the track
53 pub channel_count: u32,
54}
55
56/// Information about a frame of media.
57#[repr(C)]
58#[allow(non_camel_case_types)]
59pub struct moq_frame {
60 /// The payload of the frame, or NULL/0 if the stream has ended
61 pub payload: *const u8,
62 pub payload_size: usize,
63
64 /// The presentation timestamp of the frame in microseconds
65 pub timestamp_us: u64,
66
67 /// Whether the frame is a keyframe, aka the start of a new group.
68 pub keyframe: bool,
69}
70
71/// Information about a broadcast announced by an origin.
72#[repr(C)]
73#[allow(non_camel_case_types)]
74pub struct moq_announced {
75 /// The path of the broadcast, NOT NULL terminated
76 pub path: *const c_char,
77 pub path_len: usize,
78
79 /// Whether the broadcast is active or has ended
80 /// This MUST toggle between true and false over the lifetime of the broadcast
81 pub active: bool,
82}
83
84/// Initialize the library with a log level.
85///
86/// This should be called before any other functions.
87/// The log_level is a string: "error", "warn", "info", "debug", "trace"
88///
89/// Returns a zero on success, or a negative code on failure.
90///
91/// # Safety
92/// - The caller must ensure that level is a valid pointer to level_len bytes of data.
93#[unsafe(no_mangle)]
94pub unsafe extern "C" fn moq_log_level(level: *const c_char, level_len: usize) -> i32 {
95 ffi::enter(move || {
96 match unsafe { ffi::parse_str(level, level_len)? } {
97 "" => moq_native::Log::default(),
98 level => moq_native::Log::new(Level::from_str(level)?),
99 }
100 .init();
101
102 Ok(())
103 })
104}
105
106/// Start establishing a connection to a MoQ server.
107///
108/// Takes origin handles, which are used for publishing and consuming broadcasts respectively.
109/// - Any broadcasts in `origin_publish` will be announced to the server.
110/// - Any broadcasts announced by the server will be available in `origin_consume`.
111/// - If an origin handle is 0, that functionality is completely disabled.
112///
113/// This may be called multiple times to connect to different servers.
114/// Origins can be shared across sessions, useful for fanout or relaying.
115///
116/// Returns a non-zero handle to the session on success, or a negative code on (immediate) failure.
117/// You should call [moq_session_close], even on error, to free up resources.
118///
119/// The callback is called on success (status 0) and later when closed (status non-zero).
120/// The `on_status` callback will NOT be called after [moq_session_close].
121///
122/// # Safety
123/// - The caller must ensure that url is a valid pointer to url_len bytes of data.
124/// - The caller must ensure that `on_status` is valid until [moq_session_close] is called.
125#[unsafe(no_mangle)]
126pub unsafe extern "C" fn moq_session_connect(
127 url: *const c_char,
128 url_len: usize,
129 origin_publish: u32,
130 origin_consume: u32,
131 on_status: Option<extern "C" fn(user_data: *mut c_void, code: i32)>,
132 user_data: *mut c_void,
133) -> i32 {
134 ffi::enter(move || {
135 let url = ffi::parse_url(url, url_len)?;
136
137 let mut state = State::lock();
138 let publish = ffi::parse_id_optional(origin_publish)?
139 .map(|id| state.origin.get(id))
140 .transpose()?
141 .map(|origin: &moq_lite::OriginProducer| origin.consume());
142 let consume = ffi::parse_id_optional(origin_consume)?
143 .map(|id| state.origin.get(id))
144 .transpose()?
145 .cloned();
146
147 let on_status = unsafe { ffi::OnStatus::new(user_data, on_status) };
148 state.session.connect(url, publish, consume, on_status)
149 })
150}
151
152/// Close a connection to a MoQ server and cancel its background task.
153///
154/// Returns a zero on success, or a negative code on failure.
155///
156/// The [moq_session_connect] `on_status` callback will NOT be called.
157#[unsafe(no_mangle)]
158pub extern "C" fn moq_session_close(session: u32) -> i32 {
159 ffi::enter(move || {
160 let session = ffi::parse_id(session)?;
161 State::lock().session.close(session)
162 })
163}
164
165/// Create an origin for publishing broadcasts.
166///
167/// Origins contain any number of broadcasts addressed by path.
168/// The same broadcast can be published to multiple origins under different paths.
169///
170/// [moq_origin_announced] can be used to discover broadcasts published to this origin.
171/// This is extremely useful for discovering what is available on the server to [moq_origin_consume].
172///
173/// Returns a non-zero handle to the origin on success.
174#[unsafe(no_mangle)]
175pub extern "C" fn moq_origin_create() -> i32 {
176 ffi::enter(move || State::lock().origin.create())
177}
178
179/// Publish a broadcast to an origin.
180///
181/// The broadcast will be announced to any origin consumers, such as over the network.
182///
183/// Returns a zero on success, or a negative code on failure.
184///
185/// # Safety
186/// - The caller must ensure that path is a valid pointer to path_len bytes of data.
187#[unsafe(no_mangle)]
188pub unsafe extern "C" fn moq_origin_publish(origin: u32, path: *const c_char, path_len: usize, broadcast: u32) -> i32 {
189 ffi::enter(move || {
190 let origin = ffi::parse_id(origin)?;
191 let path = unsafe { ffi::parse_str(path, path_len)? };
192 let broadcast = ffi::parse_id(broadcast)?;
193
194 let mut state = State::lock();
195 let broadcast = state.publish.get(broadcast)?.consume();
196 state.origin.publish(origin, path, broadcast)
197 })
198}
199
200/// Learn about all broadcasts published to an origin.
201///
202/// The callback is called with an announced ID when a new broadcast is published.
203///
204/// - [moq_origin_announced_info] is used to query information about the broadcast.
205/// - [moq_origin_announced_close] is used to stop receiving announcements.
206///
207/// Returns a non-zero handle on success, or a negative code on failure.
208///
209/// # Safety
210/// - The caller must ensure that `on_announce` is valid until [moq_origin_announced_close] is called.
211#[unsafe(no_mangle)]
212pub unsafe extern "C" fn moq_origin_announced(
213 origin: u32,
214 on_announce: Option<extern "C" fn(user_data: *mut c_void, announced: i32)>,
215 user_data: *mut c_void,
216) -> i32 {
217 ffi::enter(move || {
218 let origin = ffi::parse_id(origin)?;
219 let on_announce = unsafe { ffi::OnStatus::new(user_data, on_announce) };
220 State::lock().origin.announced(origin, on_announce)
221 })
222}
223
224/// Query information about a broadcast discovered by [moq_origin_announced].
225///
226/// The destination is filled with the broadcast information.
227///
228/// Returns a zero on success, or a negative code on failure.
229///
230/// # Safety
231/// - The caller must ensure that `dst` is a valid pointer to a [moq_announced] struct.
232#[unsafe(no_mangle)]
233pub unsafe extern "C" fn moq_origin_announced_info(announced: u32, dst: *mut moq_announced) -> i32 {
234 ffi::enter(move || {
235 let announced = ffi::parse_id(announced)?;
236 let dst = unsafe { dst.as_mut() }.ok_or(Error::InvalidPointer)?;
237 State::lock().origin.announced_info(announced, dst)
238 })
239}
240
241/// Stop receiving announcements for broadcasts published to an origin.
242///
243/// Returns a zero on success, or a negative code on failure.
244#[unsafe(no_mangle)]
245pub extern "C" fn moq_origin_announced_close(announced: u32) -> i32 {
246 ffi::enter(move || {
247 let announced = ffi::parse_id(announced)?;
248 State::lock().origin.announced_close(announced)
249 })
250}
251
252/// Consume a broadcast from an origin by path.
253///
254/// Returns a non-zero handle to the broadcast on success, or a negative code on failure.
255///
256/// # Safety
257/// - The caller must ensure that path is a valid pointer to path_len bytes of data.
258#[unsafe(no_mangle)]
259pub unsafe extern "C" fn moq_origin_consume(origin: u32, path: *const c_char, path_len: usize) -> i32 {
260 ffi::enter(move || {
261 let origin = ffi::parse_id(origin)?;
262 let path = unsafe { ffi::parse_str(path, path_len)? };
263
264 let mut state = State::lock();
265 let broadcast = state.origin.consume(origin, path)?;
266 state.consume.start(broadcast)
267 })
268}
269
270/// Close an origin and clean up its resources.
271///
272/// Returns a zero on success, or a negative code on failure.
273#[unsafe(no_mangle)]
274pub extern "C" fn moq_origin_close(origin: u32) -> i32 {
275 ffi::enter(move || {
276 let origin = ffi::parse_id(origin)?;
277 State::lock().origin.close(origin)
278 })
279}
280
281/// Create a new broadcast for publishing media tracks.
282///
283/// Returns a non-zero handle to the broadcast on success, or a negative code on failure.
284#[unsafe(no_mangle)]
285pub extern "C" fn moq_publish_create() -> i32 {
286 ffi::enter(move || State::lock().publish.create())
287}
288
289/// Close a broadcast and clean up its resources.
290///
291/// Returns a zero on success, or a negative code on failure.
292#[unsafe(no_mangle)]
293pub extern "C" fn moq_publish_close(broadcast: u32) -> i32 {
294 ffi::enter(move || {
295 let broadcast = ffi::parse_id(broadcast)?;
296 State::lock().publish.close(broadcast)
297 })
298}
299
300/// Create a new media track for a broadcast
301///
302/// All frames in [moq_publish_media_frame] must be written in decode order.
303/// The `format` controls the encoding, both of `init` and frame payloads.
304///
305/// Returns a non-zero handle to the track on success, or a negative code on failure.
306///
307/// # Safety
308/// - The caller must ensure that format is a valid pointer to format_len bytes of data.
309/// - The caller must ensure that init is a valid pointer to init_size bytes of data.
310#[unsafe(no_mangle)]
311pub unsafe extern "C" fn moq_publish_media_ordered(
312 broadcast: u32,
313 format: *const c_char,
314 format_len: usize,
315 init: *const u8,
316 init_size: usize,
317) -> i32 {
318 ffi::enter(move || {
319 let broadcast = ffi::parse_id(broadcast)?;
320 let format = unsafe { ffi::parse_str(format, format_len)? };
321 let init = unsafe { ffi::parse_slice(init, init_size)? };
322
323 State::lock().publish.media_ordered(broadcast, format, init)
324 })
325}
326
327/// Remove a track from a broadcast.
328///
329/// Returns a zero on success, or a negative code on failure.
330#[unsafe(no_mangle)]
331pub extern "C" fn moq_publish_media_close(export: u32) -> i32 {
332 ffi::enter(move || {
333 let export = ffi::parse_id(export)?;
334 State::lock().publish.media_close(export)
335 })
336}
337
338/// Write data to a track.
339///
340/// The encoding of `data` depends on the track `format`.
341/// The timestamp is in microseconds.
342///
343/// Returns a zero on success, or a negative code on failure.
344///
345/// # Safety
346/// - The caller must ensure that payload is a valid pointer to payload_size bytes of data.
347#[unsafe(no_mangle)]
348pub unsafe extern "C" fn moq_publish_media_frame(
349 media: u32,
350 payload: *const u8,
351 payload_size: usize,
352 timestamp_us: u64,
353) -> i32 {
354 ffi::enter(move || {
355 let media = ffi::parse_id(media)?;
356 let payload = unsafe { ffi::parse_slice(payload, payload_size)? };
357 let timestamp = hang::container::Timestamp::from_micros(timestamp_us)?;
358 State::lock().publish.media_frame(media, payload, timestamp)
359 })
360}
361
362/// Create a catalog consumer for a broadcast.
363///
364/// The callback is called with a catalog ID when a new catalog is available.
365/// The catalog ID can be used to query video/audio track information.
366///
367/// Returns a non-zero handle on success, or a negative code on failure.
368///
369/// # Safety
370/// - The caller must ensure that `on_catalog` is valid until [moq_consume_catalog_close] is called.
371#[unsafe(no_mangle)]
372pub unsafe extern "C" fn moq_consume_catalog(
373 broadcast: u32,
374 on_catalog: Option<extern "C" fn(user_data: *mut c_void, catalog: i32)>,
375 user_data: *mut c_void,
376) -> i32 {
377 ffi::enter(move || {
378 let broadcast = ffi::parse_id(broadcast)?;
379 let on_catalog = unsafe { ffi::OnStatus::new(user_data, on_catalog) };
380 State::lock().consume.catalog(broadcast, on_catalog)
381 })
382}
383
384/// Close a catalog consumer and cancel its background task.
385///
386/// This only stops the background subscription; catalog snapshots previously
387/// delivered via the [moq_consume_catalog] callback remain valid until freed
388/// with [moq_consume_catalog_free].
389///
390/// Returns a zero on success, or a negative code on failure.
391#[unsafe(no_mangle)]
392pub extern "C" fn moq_consume_catalog_close(catalog: u32) -> i32 {
393 ffi::enter(move || {
394 let catalog = ffi::parse_id(catalog)?;
395 State::lock().consume.catalog_close(catalog)
396 })
397}
398
399/// Free a catalog snapshot received via the [moq_consume_catalog] callback.
400///
401/// This releases the snapshot and invalidates any borrowed references (e.g. pointers
402/// returned by [moq_consume_video_config] or [moq_consume_audio_config]).
403///
404/// Returns a zero on success, or a negative code on failure.
405#[unsafe(no_mangle)]
406pub extern "C" fn moq_consume_catalog_free(catalog: u32) -> i32 {
407 ffi::enter(move || {
408 let catalog = ffi::parse_id(catalog)?;
409 State::lock().consume.catalog_free(catalog)
410 })
411}
412
413/// Query information about a video track in a catalog.
414///
415/// The destination is filled with the video track information.
416///
417/// Returns a zero on success, or a negative code on failure.
418///
419/// # Safety
420/// - The caller must ensure that `dst` is a valid pointer to a [moq_video_config] struct.
421/// - The caller must ensure that `dst` is not used after [moq_consume_catalog_free] is called.
422#[unsafe(no_mangle)]
423pub unsafe extern "C" fn moq_consume_video_config(catalog: u32, index: u32, dst: *mut moq_video_config) -> i32 {
424 ffi::enter(move || {
425 let catalog = ffi::parse_id(catalog)?;
426 let index = index as usize;
427 let dst = unsafe { dst.as_mut() }.ok_or(Error::InvalidPointer)?;
428 State::lock().consume.video_config(catalog, index, dst)
429 })
430}
431
432/// Query information about an audio track in a catalog.
433///
434/// The destination is filled with the audio track information.
435///
436/// Returns a zero on success, or a negative code on failure.
437///
438/// # Safety
439/// - The caller must ensure that `dst` is a valid pointer to a [moq_audio_config] struct.
440/// - The caller must ensure that `dst` is not used after [moq_consume_catalog_free] is called.
441#[unsafe(no_mangle)]
442pub unsafe extern "C" fn moq_consume_audio_config(catalog: u32, index: u32, dst: *mut moq_audio_config) -> i32 {
443 ffi::enter(move || {
444 let catalog = ffi::parse_id(catalog)?;
445 let index = index as usize;
446 let dst = unsafe { dst.as_mut() }.ok_or(Error::InvalidPointer)?;
447 State::lock().consume.audio_config(catalog, index, dst)
448 })
449}
450
451/// Consume a video track from a broadcast, delivering frames in order.
452///
453/// - `max_latency_ms` controls the maximum amount of buffering allowed before skipping a GoP.
454/// - `on_frame` is called with a frame ID when a new frame is available.
455///
456/// Returns a non-zero handle to the track on success, or a negative code on failure.
457///
458/// # Safety
459/// - The caller must ensure that `on_frame` is valid until the track is closed.
460#[unsafe(no_mangle)]
461pub unsafe extern "C" fn moq_consume_video_ordered(
462 catalog: u32,
463 index: u32,
464 max_latency_ms: u64,
465 on_frame: Option<extern "C" fn(user_data: *mut c_void, frame: i32)>,
466 user_data: *mut c_void,
467) -> i32 {
468 ffi::enter(move || {
469 let catalog = ffi::parse_id(catalog)?;
470 let index = index as usize;
471 let max_latency = std::time::Duration::from_millis(max_latency_ms);
472 let on_frame = unsafe { ffi::OnStatus::new(user_data, on_frame) };
473 State::lock()
474 .consume
475 .video_ordered(catalog, index, max_latency, on_frame)
476 })
477}
478
479/// Close a video track consumer and clean up its resources.
480///
481/// Returns a zero on success, or a negative code on failure.
482#[unsafe(no_mangle)]
483pub extern "C" fn moq_consume_video_close(track: u32) -> i32 {
484 ffi::enter(move || {
485 let track = ffi::parse_id(track)?;
486 State::lock().consume.track_close(track)
487 })
488}
489
490/// Consume an audio track from a broadcast, emitting the frames in order.
491///
492/// The callback is called with a frame ID when a new frame is available.
493/// The `max_latency_ms` parameter controls how long to wait before skipping frames.
494///
495/// Returns a non-zero handle to the track on success, or a negative code on failure.
496///
497/// # Safety
498/// - The caller must ensure that `on_frame` is valid until [moq_consume_audio_close] is called.
499#[unsafe(no_mangle)]
500pub unsafe extern "C" fn moq_consume_audio_ordered(
501 catalog: u32,
502 index: u32,
503 max_latency_ms: u64,
504 on_frame: Option<extern "C" fn(user_data: *mut c_void, frame: i32)>,
505 user_data: *mut c_void,
506) -> i32 {
507 ffi::enter(move || {
508 let catalog = ffi::parse_id(catalog)?;
509 let index = index as usize;
510 let max_latency = std::time::Duration::from_millis(max_latency_ms);
511 let on_frame = unsafe { ffi::OnStatus::new(user_data, on_frame) };
512 State::lock()
513 .consume
514 .audio_ordered(catalog, index, max_latency, on_frame)
515 })
516}
517
518/// Close an audio track consumer and clean up its resources.
519///
520/// Returns a zero on success, or a negative code on failure.
521#[unsafe(no_mangle)]
522pub extern "C" fn moq_consume_audio_close(track: u32) -> i32 {
523 ffi::enter(move || {
524 let track = ffi::parse_id(track)?;
525 State::lock().consume.track_close(track)
526 })
527}
528
529/// Get a chunk of a frame's payload.
530///
531/// Read the payload of a frame as a single contiguous slice.
532///
533/// Frames are not chunked; the entire payload is delivered through `dst.payload` /
534/// `dst.payload_size` in one call. The pointer is valid until [`moq_consume_frame_close`]
535/// is called for this frame.
536///
537/// Returns a zero on success, or a negative code on failure.
538///
539/// # Safety
540/// - The caller must ensure that `dst` is a valid pointer to a [moq_frame] struct.
541#[unsafe(no_mangle)]
542pub unsafe extern "C" fn moq_consume_frame(frame: u32, dst: *mut moq_frame) -> i32 {
543 ffi::enter(move || {
544 let frame = ffi::parse_id(frame)?;
545 let dst = unsafe { dst.as_mut() }.ok_or(Error::InvalidPointer)?;
546 State::lock().consume.frame(frame, dst)
547 })
548}
549
550/// Close a frame and clean up its resources.
551///
552/// Returns a zero on success, or a negative code on failure.
553#[unsafe(no_mangle)]
554pub extern "C" fn moq_consume_frame_close(frame: u32) -> i32 {
555 ffi::enter(move || {
556 let frame = ffi::parse_id(frame)?;
557 State::lock().consume.frame_close(frame)
558 })
559}
560
561/// Close a broadcast consumer and clean up its resources.
562///
563/// Returns a zero on success, or a negative code on failure.
564#[unsafe(no_mangle)]
565pub extern "C" fn moq_consume_close(consume: u32) -> i32 {
566 ffi::enter(move || {
567 let consume = ffi::parse_id(consume)?;
568 State::lock().consume.close(consume)
569 })
570}