Skip to content

Publish a track

The hub can publish a track of its own. This page publishes hello as track greeting; the client SUBSCRIBEs and receives it.

Code

main.c
#define WIRED_MAIN
#include "wired.h"
static wired_moqt_hub g_hub;
int wired_main(int argc, char** argv) {
/* The same fixed demo identity as the HTTP/3 pages, plus QUIC DATAGRAM
* support, which WebTransport clients ask for. */
static wired_srvboot_demo_keys keys;
wired_srvboot_id id;
wired_srvboot_demo(&id, &keys, 0x50, "guide-mp");
id.max_datagram_frame_size = 65535;
wired_moqt_io io = wired_moqraw_io();
wired_moqt_init(&g_hub, io);
/* Publish "hello" as track "greeting" (Track Alias 1). The hub frames it
* once into wire and sends it to every peer that SUBSCRIBEs. */
static u8 wire[MOQDATA_BLOB_WIRE_CAP(5)];
wired_moqt_publish_blob(
&g_hub, wired_span_cstr("greeting"), 1, wired_span_cstr("hello"),
wired_mspan_of(wire, sizeof wire));
/* The hub owns every WebTransport session. A client that offers the
* subprotocol "moqt-22" gets draft-ietf-moq-transport-22: the hub sends
* SETUP on a uni control stream of its own (6.3), answers each request
* on the bidi stream it arrived on (6.4.2), and forgets it on close. */
wired_srvrun_opt opt = {0};
opt.incoming_cpu = -1;
opt.wt_protocols = "moqt-22";
opt.wt_on_session = wired_moqt_on_session;
opt.wt_session_ctx = &g_hub;
opt.wt_on_stream_data = wired_moqt_on_stream_data;
opt.wt_stream_data_ctx = &g_hub;
opt.wt_on_datagram = wired_moqt_on_datagram;
opt.wt_datagram_ctx = &g_hub;
opt.wt_on_session_close = wired_moqt_on_session_close;
opt.wt_session_close_ctx = &g_hub;
u16 port = (u16)wired_cliargs_int(argc, argv, "--port", 4433);
wired_srvrun_handler h = {0};
wired_srvrun_obs obs = {0};
return wired_server_run_opt(port, &id, h, obs, &opt) ? 0 : 1;
}

Client

client.go
package main
import (
"bufio"
"context"
"fmt"
"io"
"log"
"os"
"time"
"wired-guide/moqtclient"
)
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// The session handshake of the hub page: subprotocol "moqt-22", then
// one SETUP each way on the two uni control streams.
s := moqtclient.Dial(ctx, os.Args[1]+"/moqt")
defer s.Conn.CloseWithError(0, "")
fmt.Println("status", s.Status)
fmt.Printf("recv SETUP type=%#x options=%d bytes\n", s.SetupType, len(s.SetupOptions))
// SUBSCRIBE (0x3) on a bidi request stream of its own: Request ID 0,
// Track Namespace ("guide"), Track Name ("greeting"), no parameters.
// The reply, SUBSCRIBE_OK, comes back on the same stream.
fmt.Println("send SUBSCRIBE guide/greeting")
reply, _ := s.Subscribe(ctx, 0, "greeting")
if !reply.Ok {
log.Fatalf("SUBSCRIBE refused, error_code=%d", reply.ErrCode)
}
fmt.Printf("recv SUBSCRIBE_OK track_alias=%d\n", reply.Alias)
// The object arrives on its own uni stream: a SUBGROUP_HEADER (Type,
// Track Alias, Group ID), then Object ID delta, payload length, payload.
uni, err := s.AcceptUniStream(ctx)
moqtclient.Check(err)
u := bufio.NewReader(uni)
// The hub sends Type 0x70: FIRST_OBJECT (0x40) set, no Subgroup ID
// field, default priority, no properties.
moqtclient.Varint(u)
alias, group, id := moqtclient.Varint(u), moqtclient.Varint(u), moqtclient.Varint(u)
payload := make([]byte, moqtclient.Varint(u))
_, err = io.ReadFull(u, payload)
moqtclient.Check(err)
fmt.Printf("recv object track_alias=%d group=%d id=%d payload=%q\n", alias, group, id, payload)
}

Output

Terminal window
$ ./moqt-publish --port 4433
conn closed by peer (CONNECTION_CLOSE)
Terminal window
$ go run . https://127.0.0.1:4433
status 200
recv SETUP type=0x2f00 options=4 bytes
send SUBSCRIBE guide/greeting
recv SUBSCRIBE_OK track_alias=1
recv object track_alias=1 group=0 id=0 payload="hello"
  • wired_moqt_publish_blob(&hub, name, alias, blob, wire) frames blob into wire once, at startup. MOQDATA_BLOB_WIRE_CAP(n) sizes wire, and wire must outlive the hub.
  • The SUBSCRIBE arrives on its own client-opened bidirectional request stream (draft-ietf-moq-transport-22 6.4.2) and is answered there. One that names the track gets SUBSCRIBE_OK (9.7) with that Track Alias. The framed bytes then go to that peer on a new unidirectional stream, through the send_uni entry of the io table.
  • The stream holds a SUBGROUP_HEADER (Track Alias, Group 0) followed by the Object (ID 0, payload hello). The hub matches the Track Name only, not the namespace.
  • Everything else is the MoQT hub page unchanged.