btw: MoQ is under active development. The APIs and protocols are still evolving and will change. Most of this documentation is AI generated until things get more stable.

Skip to content

dev.moq:moq

The Kotlin Multiplatform module for Media over QUIC.

A single Maven coordinate that publishes JVM and Android variants. Gradle metadata picks the right one for your target, so there are no per-platform artifacts to track.

Install

kotlin
// build.gradle.kts
dependencies {
    implementation("dev.moq:moq:0.2.0")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")
}

Native binaries are bundled for:

  • Android: arm64-v8a, armeabi-v7a, x86_64
  • JVM: Linux x86_64 + aarch64, macOS x86_64 + aarch64, Windows x86_64

Android uses JNI (jniLibs/), desktop JVM uses JNA (resource-classpath layout). Both are bundled in the same AAR/JAR.

Connect

kotlin
import uniffi.moq.MoqClient
import uniffi.moq.MoqOriginProducer

// Wire an origin as both publish source and consume sink for the
// typical full-duplex client. Set just one side for a subscribe-only
// or publish-only client.
val origin = MoqOriginProducer()
val client = MoqClient()
client.setPublish(origin)
client.setConsume(origin)

val session = client.connect("https://relay.example.com")

For development against a relay with a self-signed certificate, configure the client before connecting:

kotlin
val client = MoqClient()
client.setTlsDisableVerify(true)
client.setBind("127.0.0.1:0")
client.setPublish(origin)
client.setConsume(origin)
val session = client.connect("https://localhost:4443")

When you're done, signal graceful shutdown to the peer:

kotlin
session.shutdown()  // alias for cancel(0u)

A server can reject the connection on auth grounds: MoqException.Unauthorized (HTTP 401) or MoqException.Forbidden (HTTP 403). These are terminal: retrying without new credentials won't help, so handle them separately from a transient transport failure. Use the isAuth helper to catch both:

kotlin
import dev.moq.isAuth

try {
    val session = client.connect("https://relay.example.com")
} catch (e: MoqException) {
    if (e.isAuth) {
        // Prompt for credentials; don't reconnect.
    }
}

Subscribe

kotlin
import dev.moq.*
import kotlinx.coroutines.flow.collect

val consumer = origin.consume()
val announced = consumer.announced("demos/")

announced.announcements().collect { announcement ->
    val catalog = announcement.broadcast().subscribeCatalog()
    catalog.updates().collect { update ->
        println("catalog: $update")
    }
}

Publish

kotlin
import dev.moq.*
import uniffi.moq.MoqBroadcastProducer

val broadcast = MoqBroadcastProducer()
val audio = broadcast.publishMedia("opus", opusInitBytes)

origin.publish("my-stream", broadcast)

audio.writeFrame(payload, timestampUs = 0u)
audio.writeFrame(payload, timestampUs = 20_000u)
audio.finish()
broadcast.finish()

On-demand raw tracks

Use a dynamic broadcast when subscribers should be able to request raw tracks that are not published yet:

kotlin
import dev.moq.*
import uniffi.moq.MoqBroadcastProducer

val broadcast = MoqBroadcastProducer()
val dynamic = broadcast.dynamic()

origin.publish("events", broadcast)

dynamic.requestedTracks().collect { track ->
    if (track.name() == "alerts") {
        track.writeFrame("ready".encodeToByteArray())
        track.finish()
    } else {
        track.abort(404)
    }
}

Cancellation

The wrapper exposes consumers as Kotlin Flows. Cancelling the collector's coroutine scope calls cancel() on the native side via the wrapper's onCompletion hook, releasing resources promptly:

kotlin
val job = launch {
    mediaConsumer.frames().collect { frame ->
        process(frame)
    }
}

// Later:
job.cancel()  // releases native resources

Local development

To build and run the JVM tests locally:

bash
just kt check

This builds moq-ffi for the host arch, regenerates the UniFFI Kotlin bindings, drops the host cdylib into the JNA resource layout, and runs gradle :moq:jvmTest.

Android targets are opt-in via -Pandroid.enabled=true. Local builds without the Android SDK still produce a working JVM variant.

See also

Licensed under MIT or Apache-2.0