moq-kit
Software Mansion's moq-kit — native Swift (iOS) and Kotlin (Android) SDKs for Media over QUIC (moq-lite) live streaming: sub-second-latency playback…
Skill by software-mansion-labs
Install
Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.
npx agentmarket install skill/software-mansion-labs-moq-kit
Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.
Get the weekly diff
The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.
About
Use cases: frontend, data, writing
Reviews
Write a reviewNo reviews yet. Installed it? Reviews come from verified installers only. How reviews work
Permissions
What this skill can do and what it asks for, from its SKILL.md. Checked Oct 25, 2026.
- Tools
- No tools found
- Secrets
- None asked for
- Install scripts
- None found
Network hosts it talks to 1
codeload.github.com· found in install command
Files it touches
~/.claude/skills/moq-kit
What's inside
- Skill name
moq-kit- Description
- Software Mansion's moq-kit — native Swift (iOS) and Kotlin (Android) SDKs for Media over QUIC (moq-lite) live streaming: sub-second-latency playback, camera/microphone/screen publishing, and realtime data tracks over a MoQ relay. MUST USE before writing, reviewing, or debugging ANY code that imports MoQKit (Swift) or com.swmansion.moqkit (Kotlin); for React Native apps use the react-native-moq skill instead. Trigger on: 'moq-kit', 'MoQKit', 'Media over QUIC', 'moq-lite', 'MoQ relay', 'moq-ffi', 'BroadcastSubscription', 'DataTrackEmitter', 'CameraCapture', 'MicrophoneCapture', 'MultiCameraCapture', 'ScreenCapture', 'TrackSubscription', 'AudioDataStream', 'MoQReplayKitBroadcastSampleHandler', or a Session/Player/Publisher/Catalog from moq-kit.
- License
- Apache-2.0
- Instructions
- 179 lines in SKILL.md · view on GitHub
Files (9)
- SKILL.md
- data-tracks-android.md · references/
- data-tracks-ios.md · references/
- playback-android.md · references/
- playback-ios.md · references/
- publishing-android.md · references/
- publishing-ios.md · references/
- screen-capture-android.md · references/
- screen-capture-ios.md · references/
SKILL.md (first lines)
# moq-kit
Native Swift and Kotlin SDKs for Media over QUIC live streaming: connect to a relay, discover broadcasts, play catalog-described streams with sub-second latency, publish camera/microphone/screen tracks, and exchange raw data tracks. Wraps the UniFFI bindings generated from `moq-ffi` (Luke Curley's `moq-dev/moq`); targets the `moq-lite` protocol, not the IETF `moq-transport` draft.
**Requirements:** iOS 16+ / macOS 13+ (Xcode 16+ to build) / Android minSdk 29, compileSdk 35. A moq-lite relay to connect to. The references cover iOS and Android; macOS is declared by the Swift package but untested here.
moq-kit is an **active preview**: public APIs, packaging, and codec coverage can still change between releases. Pin an exact version and check signatures against the installed artifact rather than assuming a shape.
**Install — iOS (SPM):**
```swift
.package(url: "https://github.com/software-mansion-labs/moq-kit", exact: "<latest release>")
// product: .product(name: "MoQKit", package: "moq-kit")
```
**Install — Android (Maven Central):**
```kotlin
dependencies { implementation("com.swmansion.moqkit:moqkit:<latest release>") }
```
Fill in `<latest release>` from https://github.com/software-mansion-labs/moq-kit/releases. Both pins are exact on purpose — loosen the SPM one (`.upToNextMinor(from:)`) only if you mean to track preview releases as they land.
## Platform detection
Reference files are split per platform — read only the ones for the project at hand:
- `Package.swift`, `*.xcodeproj`, or a `Podfile` → the `*-ios.md` references (Swift).
- `build.gradle`/`build.gradle.kts` with `com.swmansion.moqkit` → the `*-android.md` references (Kotlin).
- Cross-platform work (feature parity, bindings on top of both SDKs) → read both; the APIs are deliberately kept equivalent.
## Mental model
- A **`Session`** owns one QUIC connection to a relay and serves both subscribing and publishing. `connect()` once; a session is **one-shot** — after `close()` (or a connection error) create a new `Session`. Swift: `actor`, `connect() async throws`. Kotlin: optionally takes a `parentScope` (defaults to an internal IO scope; pass `viewModelScope`/`lifecycleScope` so cancelling it tears the session down); `connect()` is the only suspend call.
- **Subscribing:** `session.subscribe(prefix:)` → stream of `Broadcast` → `broadcast.catalogs()` → pick playable tracks → `Player(catalog:videoTrackName:audioTrackName:targetBuffering:)` → `play()`. Rendering is platform-specific: iOS adds `player.videoLayer` (an `AVSampleBufferDisplayLayer`) to the view hierarchy; Android calls `player.setSurface(surface)` with a `Surface` you own.
- **Publishing:** create capture sources (`CameraCapture`, `MicrophoneCapture`, `MultiCameraCapture`, `ScreenCapture`) and **start them yourself** → `Publisher()` + `addVideoTrack`/`addAudioTrack`/`addDataTrack` → `session.publish(path, publisher)` → `publisher.start()`. A publisher is single-use; add all tracks before `start()`.
- **Data tracks** (`DataTrackEmitter` → `addDataTrack`; consume with `broadcast.subscribeTrack(name:)`) bypass the media catalog — publisher and subscriber agree on the track name out of band.
- Everything is observable: `session.state`, `publisher.state`/`events`, player events/stats. Kotlin uses `StateFlow`/`SharedFlow`/cold `Flow`; Swift uses `AsyncStream` for session/publisher state and diagnostics, but callback subscriptions (`subscribeEvents`/`subscribeStats`) for player events/stats.
## Quick start — watch
**iOS:**
```swift
let session = Session(url: "http://localhost:4443/anon")
try await session.connect()
let subscription = try await session.subscribe(prefix: "live")
for await broadcast in subscription.broadcasts {
for await catalog in broadcast.catalogs() {
let video = catalog.playableVideoTracks.first?.name
let audio = catalog.playableAudioTracks.first?.name
guard video != nil || audio != nil else { continue }
let player = try await MainActor.run {
try Player(catalog: catalog, videoTrackName: video, audioTrackName: audio)
}
try await player.play() // render via player.videoLayer — see playback-ios.md
}
}
```
**Android:**
```kotlin
scope.launch {
val session = Session(url = "http://localhost:4443/anon", parentScope = scope)
session.connect()
session.subscribe(prefix = "live").broadcasts.collect { broadcast ->
broadcast.catalogs().collect { catalog ->
val video = catalog.playableVideoTracks.firstOrNull()?.name
val audio = catalog.playableAudioTracks.firstOrNull()?.name
if (video == null && audio == null) return@collect
val player = Player(catalog, video, audio, parentScope = scope)
player.setSurface(surfaceView.holder.surface)
player.play()
}
}
}
```
Minimal on purpose: the nested loops handle one broadcast at a time — spawn a `Task`/`launch` per broadcast in real code — and each catalog update should tear down the previous player before creating the new one (players hold native handles).
## Quick start — go live
Same four steps on both platforms, and the order is load-bearing (rule 3):
**iOS:**
```swift
try await camera.start() // 1. captures — publisher.start() won't do this
let publisher = try Publisher() // 2. every track before start()
publisher.addVideoTrack(name: "camera", source: camera, config: VideoEncoderConfig())
try await session.publish(path: "live/ios", publisher: publisher) // 3. register
try await publisher.start() // 4. go live
```
**Android:**
```kotlin
camera.start(context, lifecycleOwner)
val publisher = Publisher()
publisher.addVideoTrack(name = "camera", source = camera, config = VideoEncoderConfig())
session.publish(path = "live/android", publisher = publisher)
publisher.start()
```
Captures, previews, encoder configs, and permissions are in the publishing references.
## Shared concepts (both platforms)
- **Catalogs:** `broadcast.catalogs()` streams catalog updates — each element **replaces** the previous; the stream ending means the broadcast went away (mark it offline). It often ends by **throwing** rather than completing — a producer that disappears surfaces as `MoqException$Mux: json: remote error: code=…` out of `collect`, so handle the exception and the clean completion identically (both demo apps and react-native-moq do). Always select from `playableVideoTracks` / `playableAudioTracks` (device-filtered on Android and for iOS audio; iOS video playability is codec-family recognition only), not the raw lists; per-track `isPlayable`/`unsupportedReason` explain exclusions (they live on the concrete `VideoTrackInfo`/`AudioTrackInfo`, not the base `TrackInfo`). Video codec strings: `"avc1"`, `"hev1"`, `"av01"`; audio: `"mp4a.40.2"`/`"aac"` (Android-published tracks advertise `"aac"`), `"opus"`. AV1 is **playback-only** — neither platform publishes it, and Apple-side decode is iPhone 15 Pro-class hardware, not a general capability.
- **Broadcast paths are prefix-relative:** subscribing with prefix `"live"` reports broadcast `live/game` as `path: "game"` — subscribe with `""` to see full paths.
- **Three observation channels on `Player`** — don't mix them up: **events** (lifecycle milestones — `player.init`, `playback.start`, `track.ready`, `track.switch`, `track.stall.start`, `rebuffer.end`, `decode.error` — wire values; Kotlin matches the `PlayerEventName` enum; not replayed), **stats** (`PlaybackStats` at ~1 s cadence — latency, fps, bitrates, `StallStats`, `timeToFirst`, buffer depths, drops, switches), and **diagnostics** (typed per-frame `PipelineEvent` — `frameDropped` with `DropStage`/`DropReason`, `stallStarted`, `latencySample`, … — bounded at 256 drop-oldest — Android hands every caller the same shared flow — and never backpressures playback; for logging/telemetry only).
- **Publisher states:** `Idle / Publishing / Stopped / Error(message)` — but Android never actually enters `Error`, and it does not react to the session dying either: an Android publisher sits in `Publishing` indefinitely after its session is gone, so the session's own state is the only signal that the broadcast stopped. Wire failure UI to **events**: `TrackStarted` / `TrackStopped` / a per-track error (Kotlin `TrackError(name, message)`, Swift `.error(name, message)`); per-track `PublishedTrack.state` (`Idle / Starting / Active / Stopped`) with `PublishedTrack.stop()` to end one track. `session.unpublish(path)` calls `publisher.stop()`.
- **`TrackDelivery`** for raw track subscription: `monotonic` (default) skips late groups — right for newest-value-wins live state; `arrival` delivers every group in order — right for chat and anything lossless.
## Critical rules
1. **Sessions, publishers, and players are single-use.** A `Session` connects once (`alreadyConnected`/`IllegalStateException` on reuse); after `close()` make a new one. A stopped `Publisher` cannot be restarted. Never restart a torn-down `Player` on either platform — create a fresh one.
2. **Media track names are local labels, catalog names are muxer-generated.** The `name:` passed to `addVideoTrack`/`addAudioTrack` (defaults `"video"`/`"audio"`) only labels `PublishedTrack` and publisher events; subscribers must discover real track names from `Catalog.videoTracks`/`audioTracks`. **Exception: data tracks** — `addDataTrack(name:)` (default `"data"`) IS the name subscribers pass to `subscribeTrack(name:)`.From software-mansion-labs/skills, Apache-2.0.
More from software-mansion-labs/skills
- 0.803score
fishjam
software-mansion-labs
Software Mansion's Fishjam — hosted WebRTC platform for video, audio, one-to-many livestreaming, and compositions.
SkillAFreshness: A★ 296Claude Code +9 - 0.757score
react-native-best-practices
software-mansion-labs
Software Mansion's best practices for production React Native and Expo apps on the New Architecture.
SkillBFreshness: B★ 296Claude Code +9 - 0.757score
react-native-moq
software-mansion-labs
Software Mansion's react-native-moq — Media over QUIC (MoQ) live streaming for React Native: sub-second-latency video/audio playback, camera/mic/screen…
SkillBFreshness: B★ 296Claude Code +9 - 0.751score
typegpu
software-mansion-labs
TypeGPU is type-safe WebGPU in TypeScript.
SkillBFreshness: B★ 296Claude Code +9 - 0.751score
pulsar-haptics
software-mansion-labs
Implement, migrate, design, review, and troubleshoot haptic feedback with Software Mansion Pulsar across React Native, iOS, Android, Kotlin Multiplatform…
SkillBFreshness: B★ 296Claude Code +9 - 0.699score
rnrepo
software-mansion-labs
Best practices for integrating and using RNRepo — Software Mansion's infrastructure for pre-built React Native library artifacts that reduces native build…
SkillCFreshness: C★ 296Claude Code +9 - 0.693score
detour-onboarding
software-mansion-labs
Complete onboarding guide for developers who are new to Detour, the open-source deferred deep linking SDK by Software Mansion.
SkillCFreshness: C★ 296Claude Code +9 - 0.693score
migrate-to-detour
software-mansion-labs
Use when the user mentions migrating deep links, switching away from Branch or AppsFlyer, replacing their deep linking SDK, setting up Detour deep linking for…
SkillCFreshness: C★ 296Claude Code +9 - 0.619score
radon-mcp
software-mansion-labs
Best practices for using Radon IDE's MCP tools when developing, debugging, and inspecting React Native and Expo apps.
SkillDFreshness: D★ 296Claude Code +9 - 0.533score
expo-horizon
software-mansion-labs
Software Mansion's guide for migrating Expo SDK apps to Meta Quest using expo-horizon packages.
SkillDFreshness: D★ 296Claude Code +9
Compare and alternatives
Featured in
Signals
- Stars
- 296
- Forks
- 21
- Open issues
- 3
- Last commit (this folder)
- Aug 12, 2026 (2 months ago)
From GitHub (fetched Oct 24, 2026).
Details
- License
- Apache-2.0
- Repository
- github.com/software-mansion-labs/skills
- First seen
- Oct 24, 2026
- Last checked
- Oct 24, 2026
- Source
- GitHubgithub-graphql
More from software-mansion-labs
- 0.803score
fishjam
software-mansion-labs
Software Mansion's Fishjam — hosted WebRTC platform for video, audio, one-to-many livestreaming, and compositions.
SkillAFreshness: A★ 296Claude Code +9 - 0.757score
react-native-best-practices
software-mansion-labs
Software Mansion's best practices for production React Native and Expo apps on the New Architecture.
SkillBFreshness: B★ 296Claude Code +9 - 0.757score
react-native-moq
software-mansion-labs
Software Mansion's react-native-moq — Media over QUIC (MoQ) live streaming for React Native: sub-second-latency video/audio playback, camera/mic/screen…
SkillBFreshness: B★ 296Claude Code +9 - 0.751score
typegpu
software-mansion-labs
TypeGPU is type-safe WebGPU in TypeScript.
SkillBFreshness: B★ 296Claude Code +9 - 0.751score
pulsar-haptics
software-mansion-labs
Implement, migrate, design, review, and troubleshoot haptic feedback with Software Mansion Pulsar across React Native, iOS, Android, Kotlin Multiplatform…
SkillBFreshness: B★ 296Claude Code +9 - 0.699score
rnrepo
software-mansion-labs
Best practices for integrating and using RNRepo — Software Mansion's infrastructure for pre-built React Native library artifacts that reduces native build…
SkillCFreshness: C★ 296Claude Code +9
Cite this page
moq-kit skill. Agent Mart. https://agentmart.dev/skills/software-mansion-labs-moq-kit (last updated 2026-10-24)