Play

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)
  1. A seat and a token. The app gets them the way a web page does: your backend calls POST /v1/streams/{id}/viewer-tokens with 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}/admit with the publishable key, as the web player does. You need four things from the answer: relays[0].url, namespace, token, and for the seat play_url and heartbeat_ms.
  2. The library. nearglass_sub_new connects and subscribes to one track (video); frames arrive on a callback.
  3. The heartbeat. The seat is kept by your app, not the library: POST {play_url}/hb with {"seat":"se_..."} every heartbeat_ms (5 s). A seat with no heartbeat for 20 s is freed and its relay session closed. POST {play_url}/leave when 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.h264

The 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:

FlagMeaning
NEARGLASS_FRAME_HIDDENdecode it, do not show it: the start of an intra refresh
NEARGLASS_FRAME_LATElater 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 exp plus 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 the seat back to viewer-tokens), start a second nearglass_sub with 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_new returns 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_free waits 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's relay_cert_sha256 is a hash for browsers and is not used by the library. A relay with a CA-issued certificate takes NULL.
  • A viewer-side control sender (driving a machine) is not in the C library: use the Rust nearglass_sub::Controller, or the browser's createController (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.c built with the Linux line from the sample's README and loaded lib/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_new succeeded 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.c compiled with -Wall -Wextra -Werror against include/nearglass.h and linked with the packaged libnearglass_ffi.a.

On this page