Native Windows viewer
Play a stream in your own Windows app with the C library nearglass.h, loaded at run time, with your own decoder.
For a viewer that is not a web page, the SDK has a C library, nearglass_ffi,
with the header nearglass.h. It connects to a relay with a viewer token and
hands your code each video frame as it arrives. You bring the decoder and the
window: Media Foundation on Windows, or any H.264 decoder you already use.
The parts
your backend --secret key--> control API --viewer token, relay, namespace--> your app
your app --nearglass_sub_new(relay, namespace, track, token)--> relay --frames--> on_frame --> your decoder
your app --POST {play_url}/hb every 5 s--> control API (keeps the seat)- A seat and a token. The app gets them the way a web page does: your
backend calls
POST /v1/streams/{id}/viewer-tokenswith the secret key (Mint viewer tokens on your server) and hands the app the answer. Or, for a public stream, the app POSTs/v1/play/{id}/admitwith the publishable key, as the web player does. You need four things from the answer:relays[0].url,namespace,token, and for the seatplay_urlandheartbeat_ms. - The library.
nearglass_sub_newconnects and subscribes to one track (video); frames arrive on a callback. - The heartbeat. The seat is kept by your app, not the library:
POST {play_url}/hbwith{"seat":"se_..."}everyheartbeat_ms(5 s). A seat with no heartbeat for 20 s is freed and its relay session closed.POST {play_url}/leavewhen the app closes.
The sample
samples/native-viewer is one C file that
does the library part and nothing else. It loads the library at run time
(LoadLibraryA on Windows, dlopen elsewhere), so the app has no
compile-time dependency on it and can check the library's version before it
calls anything.
# Windows (MSVC, from a Developer Command Prompt)
cl /O2 viewer.c
viewer.exe nearglass_ffi.dll https://relay.nearglass.com/tn_... - viewer.tok live/st_... video 10 out.h264The arguments: the library, the relay URL, a PEM file of the relay's CA
(- for the system's roots, which is right for a relay with a normal
certificate), a file holding the token (- for none), the namespace, the
track, how many seconds to run, and optionally a file for the Annex B stream.
It prints one JSON line (frames, keyframes, hidden, late, bytes written) and
exits 3 if no frame came.
The shape of a viewer, from the header:
#include "nearglass.h"
static void on_frame(void *user, const nearglass_frame *f) {
/* Runs on the library's thread: copy f->data into your decoder's queue and
return. Never block here, never do I/O. The pointer is valid only during the call. */
if (f->flags & (NEARGLASS_FRAME_HIDDEN | NEARGLASS_FRAME_LATE)) { /* decode it, do not show it */ }
}
int main(void) {
if (nearglass_abi_version() != NEARGLASS_ABI_VERSION) return 1;
char err[256];
nearglass_conn conn = { "https://relay.nearglass.com/tn_...", NULL /* system roots */, token_base64url };
nearglass_sub *s = nearglass_sub_new(&conn, "live/st_...", "video", err, sizeof err);
if (!s) { fprintf(stderr, "%s\n", err); return 1; }
nearglass_sub_set_frame_callback(s, on_frame, NULL);
/* ... run your window; heartbeat the seat ... */
nearglass_sub_free(s); /* no callback runs after this returns */
}What a frame is
nearglass_frame: the access unit (data, len, Annex B H.264), its sequence
number seq, capture_us (the publisher's clock) and arrival_us (this
host's clock, when the last byte came), both in microseconds since the Unix
epoch, keyframe, epoch (it changes when the publisher restarts), and
flags:
| Flag | Meaning |
|---|---|
NEARGLASS_FRAME_HIDDEN | decode it, do not show it: the start of an intra refresh |
NEARGLASS_FRAME_LATE | later than the stale budget: decode it (later frames refer to it), do not show it |
arrival_us - capture_us is the path time only if the two clocks agree.
nearglass_frame.flags is ABI 2; a library that reports ABI 1 has no flags,
so read it only when nearglass_abi_version() is 2 or more (the sample does).
Rules for the callback: it runs on a thread the library owns, it must not block, and the data is valid only during the call. A decoder that falls behind should drop frames at your own queue and count them, as the sample does; it should not stall the callback.
Things your app must do
- Renew the token. The library connects with the one token you give it.
A relay ends a viewer's session shortly after the token expires (at
expplus a grace period the relay is set to), and the library does not fetch a new token. Before that, ask your backend for a new token for the same seat (pass theseatback toviewer-tokens), start a secondnearglass_subwith it, and free the first once the second delivers. A seat holds its two newest tokens, which is what makes this possible. The browser player does this itself (Token renewal). - Keep the seat. Heartbeat it, and give it up on exit (above).
- Reconnects. By its design the library reconnects and rejoins the stream by itself if the connection drops, with the same token (so not after the token has run out); there is no callback for it.
- Failures are silent.
nearglass_sub_newreturns a handle once it has started, not once it has connected. A wrong CA, an expired token or an unreachable relay show up as frames that never come, not as an error. Watch for "no frame within a few seconds" yourself and tell your viewer. - Stop calling a handle before freeing it.
nearglass_sub_freewaits for the library to unsubscribe (a fraction of a second) and no callback runs after it returns.
Errors: constructors return NULL and write a message into err (for bad
arguments, not for a failed connection, above); other functions return 0 or
a negative code (NEARGLASS_ERR_ARG, NEARGLASS_ERR_QUEUE,
NEARGLASS_ERR_CLOSED).
Limits
- One track per
nearglass_sub, video. Sound through this library is not covered here. - No LL-HLS fallback: this path is MoQ only. A viewer that cannot reach the relay over QUIC gets no picture. Use the web player there.
- A relay with a self-signed certificate (a test setup) needs its CA as a PEM
file in
tls_root; the API'srelay_cert_sha256is a hash for browsers and is not used by the library. A relay with a CA-issued certificate takesNULL. - A viewer-side control sender (driving a machine) is not in the C library:
use the Rust
nearglass_sub::Controller, or the browser'screateController(Controller seats).
What was run
On a Linux build host, from the packaged library (nearglass-ffi-<version>-linux-x86_64.tar.gz), against the managed service stood up on one machine:
viewer.cbuilt with the Linux line from the sample's README and loadedlib/libnearglass_ffi.so. With a viewer token and the relay's CA it read ABI 2, received the stream (about 700 frames in 12 s, which includes the backlog at the join), and wrote the Annex B file.- With a relay certificate the system does not trust and no CA given, the
call to
nearglass_sub_newsucceeded and no frame ever came, exit status 3. The library does not report a failed connection through the API. - A session did not outlive its token. Two fresh, heartbeated seats, one with a 10 s token and one with a 120 s token, each watched for 60 s by the viewer: the 10 s token delivered about 71 % of the frames of the 120 s one, which is a session that ended roughly 42 s in, at the token's expiry plus the 30 s grace that test relay was set to, and then stayed ended. The library did not renew it.
crates/nearglass-ffi/examples/subscribe.ccompiled with-Wall -Wextra -Werroragainstinclude/nearglass.hand linked with the packagedlibnearglass_ffi.a.