Developers

Hackathon spotlight: bringing Media over QUIC into Bitmovin’s Live Encoder

Adam Massaro
. 4 min read

TL;DR

  • Bitmovin’s Player supports MoQ playback but our Live Encoder had no native MoQ support. This hackathon was about closing that gap: MoQ in, encode, MoQ out, play in Bitmovin’s Player.
  • We built a MoQ bridge and MoQ sender inside the encoder, a custom MPEG-TS publisher to handle containerized MoQ input, and our own relay on EC2. MoQ input worked end to end.
  • MoQ output did not complete: two bugs in BCF blocked the stream. The issues are identified, the fix path is clear, and a full end-to-end workflow is close.

Live streaming protocols have long been shaped by the constraints of TCP. RTMP and SRT both work around those constraints in different ways, but neither was designed for the low-latency, resilient delivery that modern live streaming demands. MoQ (Media over QUIC Transport) takes a different approach: built natively on QUIC and WebTransport, packet loss, connection migration, and multiplexed streams are handled at the protocol level rather than worked around. Bitmovin is a member of the OpenMoQ consortium, and our Player already supports MoQ playback. The missing piece was the encoder.

This hackathon we set out to close that loop. In this post we walk through how we designed the integration, what we got working, where we ran into trouble, and what the path forward looks like.

What we set out to build

The goal was a full end-to-end MoQ workflow: accept a live MoQ stream as input, encode it through BCF (our internal encoder core), output the result back over MoQ to a relay, and play it back in Bitmovin’s Player. To understand what that required, it helps to know how a MoQ relay fits into the picture. A relay is a server that sits between publishers and subscribers, receiving MoQ streams from one side and forwarding them to the other. In our setup we used moq.rs, a reference relay implementation, running in a container on EC2. It handled both the input side (receiving the stream before it enters the encoder) and the output side (receiving encoded output and forwarding it to the player).

The full MoQ workflow: publisher to input relay, through Bitmovin’s Live Encoder, and back out through the output relay to the player

With the relay in place, we needed three new components on the encoder side: something to bridge MoQ input into the encoder, something to send encoded output back over MoQ, and a publisher to generate the input stream in the right format. Each of those is described below.

The components we built

BCF understands SRT, RTMP, and MPEG-TS as ingest sources. It has no native concept of MoQ. To get MoQ input working, we built the moq-udp-bridge: a component that subscribes to the input relay, reads the incoming MoQ stream, and forwards it to BCF over UDP as MPEG-TS, the same way a Zixi or SRT source would. From BCF’s perspective it looks like any other input.

On the output side, BCF has an existing ingest output type used to transport encoded content to external packagers, for example sending CMAF over HTTP. We used this as the model for our MoQ output component, the moq-sender, which takes encoded chunks from BCF and sends them as CMAF over MoQ to the output relay. The tradeoff was that mimicking the existing ingest output meant inheriting its internal business logic and configuration structure, which added complexity we would avoid if we were designing from scratch.

The third piece was the publisher. Standard MoQ tools like moq-cli and OBS output raw web codec streams with no container, but BCF requires containerized input (MPEG-TS or FLV). Since nothing existing handled this, we built moq-mpegts-publisher: a small service that wraps the stream in MPEG-TS before publishing it to the input relay over MoQ. One other finding worth noting: the encoder needed to support two catalog announcement formats, Hang (used by moq-lite and most Rust tooling) and the IETF draft standard (used by Bitmovin’s Player). We added support for both catalog modes so players on either side can read the output.

What we achieved

MoQ input into the encoder worked. The screenshot below shows an active live encoding session with a MoQ input connected, traffic confirmed in the monitoring tab.

A live Bitmovin’s Live Encoder encoding session with an active MoQ input connection and traffic confirmed in the monitoring tab

On the output side, we ran the standalone moq-sender against Bitmovin’s Player directly, bypassing BCF, and confirmed playback with browser developer tools showing active WebTransport connections. So we had MoQ input into the encoder working, and MoQ output from the sender to the player working. The piece that did not come together was getting encoded output from BCF through the moq-sender and out to the relay in a complete stream.

Other completed work across the hackathon:

  • Full API implementation with in-memory repository for testing and DB migrations ready for production
  • moq.rs relay running on EC2, handling both input and output roles correctly
  • moq-udp-bridge integrated into the encoder service with no issues
  • moq-sender built, integrated into BCF, and bugs fixed
  • moq-mpegts-publisher producing valid containerized MPEG-TS streams

Where we fell short, and why

Two bugs inside BCF prevented the moq-sender from producing a valid output stream. We have identified both and know how to fix them, but ran out of hackathon time. We are keeping the details internal for now given the components involved are still in active development.

We also cost ourselves time by modelling the moq-sender too closely on the existing ingest output service. The intent was to fit cleanly into the existing architecture, but it meant inheriting internal configuration complexity that slowed iteration significantly. Every dependency change triggered a multi-hour build pipeline. If we do this again, we would define a clean standalone API from the start rather than mimicking an existing service.

Key takeaways

Design your components before picking your integration point. Starting from an existing internal service to save time ended up costing us more. The moq-udp-bridge approach on the input side, building something purpose-fit rather than imitating existing code, worked much better.

The MoQ standard is split in practice. Most Rust tooling runs on Hang/moq-lite, an unofficial lightweight implementation by Luke Curley. Bitmovin’s Player uses the IETF catalog format. We had to support both catalog modes to cover both sides, which was not obvious going in.

The Rust ecosystem for MoQ is solid. Outside of the catalog split, the available libraries got us moving quickly and held up well throughout.

We are close. Both ends of the pipeline work in isolation. Two identified bugs in BCF are what stands between where we are now and a complete end-to-end MoQ workflow.

Adam Massaro

Adam Massaro

Product Marketing Manager

Adam has spent more than 10 years in the video industry, helping turn complex technology and industry trends into clear, engaging stories. At Bitmovin, as a Senior Product Marketing Manager, his work covers the full portfolio, including playback, observability, VOD and live encoding, and AI Scene Analysis. His focus is on making these technologies easier to understand and relevant to the challenges video teams face today.


Related Posts

Join our newsletter and stay informed.

Get to hear first when we publish something new.