Skip to content

Repository files navigation

Membrane MoQ Plugin

Hex.pm API Docs

Membrane plugin for Media over QUIC (MoQ) streams:

  • Membrane.MoQ.Sink publishes tracks to a broadcast on a MoQ relay.
  • Membrane.MoQ.Source subscribes to a broadcast's tracks and emits their frames, notifying its parent as tracks come and go.

The MoQ session, catalog and wire handling are implemented natively on top of the moq Rust stack (moq-native, moq-mux, hang), bound via Rustler NIFs.

Published broadcasts advertise both the hang catalog and the IETF MSF catalog, so they interoperate with the moq CLI, moq-gst and the JS @moq/hang player as well as MSF-based consumers. Subscribing picks the catalog from the broadcast name's suffix (.msf → MSF, otherwise hang).

Publishing encapsulates frames in the :legacy or :loc wire container. Consuming selects each track's container from the catalog automatically.

It is a part of Membrane Multimedia Framework.

Installation

The package can be installed by adding membrane_moq_plugin to your list of dependencies in mix.exs:

def deps do
  [
    {:membrane_moq_plugin, "~> 0.1.0"}
  ]
end

Building requires a Rust toolchain (the NIF is compiled by rustler)

Usage

Both elements talk to a MoQ relay. For local development run one with anonymous auth, e.g. moq-relay (cargo install moq-relay). The examples assume https://localhost:4443 with a self-signed certificate.

Publishing an H.264 track:

child(:source, %Membrane.File.Source{location: "video.h264"})
|> child(:parser, %Membrane.H264.Parser{
  output_stream_structure: :avc3,
  generate_best_effort_timestamps: %{framerate: {30, 1}}
})
|> child(:realtimer, Membrane.Realtimer)
|> via_in(Pad.ref(:input, :video), options: [track: "video"])
|> child(:sink, %Membrane.MoQ.Sink{
  url: "https://localhost:4443",
  broadcast: "demo.hang",
  disable_tls_verify?: true
})

Subscribing to it:

child(:source, %Membrane.MoQ.Source{
  url: "https://localhost:4443",
  broadcast: "demo.hang",
  disable_tls_verify?: true
})
|> via_out(Pad.ref(:output, :video), options: [track: "video"])
|> child(:parser, %Membrane.H264.Parser{
  generate_best_effort_timestamps: %{framerate: {30, 1}}
})

The examples/ directory has runnable scripts covering the common setups:

  • loopback publish+play (publish_and_play.exs)
  • multi-track A/V from an MP4 (publish_mp4.exs)
  • notification-driven subscribing (dynamic_subscriber.exs).

Testing

mix test runs the unit suite. Integration tests exercise a real relay and are opt-in:

mix test --include integration

Copyright and License

Copyright 2026, Software Mansion

Software Mansion

Licensed under the Apache License, Version 2.0

About

Membrane plugin for Media over QUIC (MoQ) streams

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages