Media over QUIC (MoQ) Flutter client implementation supporting draft-ietf-moq-transport-14 and draft-ietf-moq-transport-16.
lib/
├── main.dart # Application entry point
├── router.dart # GoRouter multi-screen navigation
├── moq/ # MoQ protocol implementation
│ ├── client/ # MoQ client
│ │ ├── moq_client.dart # Main client with subscription handling
│ │ └── replay_stream.dart # ReplayStreamController for late-joiners
│ ├── catalog/ # Catalog and timeline management
│ │ ├── moq_catalog.dart # JSON catalog (streamingFormat, commonTrackFields)
│ │ ├── moq_catalog_subscriber.dart # Catalog subscription and track management
│ │ └── moq_timeline.dart # Timeline management for playback/seeking
│ ├── media/ # Media capture and encoding
│ │ ├── audio_capture.dart # Platform-specific audio capture
│ │ ├── audio_encoder.dart # Opus audio encoder (FFmpeg)
│ │ ├── camera_capture.dart # Camera capture abstraction
│ │ ├── linux_capture.dart # Linux V4L2/FFmpeg video capture
│ │ ├── video_encoder.dart # H.264 video encoder (FFmpeg)
│ │ ├── media_encoder.dart # Combined audio/video encoder
│ │ ├── native_capture_channel.dart # Platform channel for native capture (Android)
│ │ ├── native_opus_encoder.dart # Native Opus encoder (opus_dart/opus_flutter)
│ │ ├── native_h264_encoder.dart # VideoToolbox H.264 encoder (macOS/iOS)
│ │ ├── moq_media_decoder.dart # CMAF/moq-mi stream decoder
│ │ ├── streaming_playback.dart # Media decode/playback pipeline
│ │ └── fmp4/ # CMAF/fMP4 packaging
│ ├── packager/ # Media packaging formats
│ │ └── moq_mi_packager.dart # MoQ Media Interop (moq-mi) packager
│ ├── publisher/ # Publishing support
│ │ ├── cmaf_publisher.dart # CMAF/fMP4 publisher (auto-forward, filter types)
│ │ ├── moq_publisher.dart # LOC publisher
│ │ └── moq_mi_publisher.dart # MoQ-MI publisher (LOC with extension headers)
│ ├── protocol/ # Protocol messages and types
│ │ ├── moq_messages.dart # Core message types and enums
│ │ ├── moq_wire_format.dart # Varint encoding/decoding
│ │ ├── moq_messages_control.dart # Control messages
│ │ ├── moq_messages_control_extra.dart # Additional control messages
│ │ ├── moq_messages_data.dart # Data messages (objects)
│ │ ├── moq_messages_publish.dart # Publish-related messages
│ │ └── moq_data_parser.dart # Data stream parser
│ └── transport/ # Transport layer abstraction
│ └── moq_transport.dart # MoQ transport interface
├── media/ # Media playback
│ ├── moq_video_player.dart # Video player service
│ └── moq_video_providers.dart # Riverpod video player providers
├── models/ # Data models
├── providers/ # Riverpod state providers
│ └── moq_providers.dart
├── screens/ # UI screens
│ ├── connection_screen.dart # Server connection UI
│ ├── publisher_screen.dart # Media publishing UI
│ ├── viewer_screen.dart # Media playback UI
│ └── settings_screen.dart # Application settings
├── services/ # Platform-specific services
│ ├── quic_transport.dart # QUIC transport via FFI
│ ├── webtransport_quinn_transport.dart # WebTransport via FFI
│ ├── settings_service.dart # Connection presets persistence
│ └── native_media_player.dart # Embedded mpv player via FFI
├── utils/ # Utility functions
└── widgets/ # Reusable widgets
native/moq_quic/ # Rust QUIC library (FFI)
├── Cargo.toml
└── src/
├── lib.rs # Quinn QUIC transport
├── webtransport.rs # WebTransport adapter
├── stream_writer.rs # Non-blocking write queue
└── media_player.rs # Embedded mpv player
android/app/src/main/kotlin/
└── MainActivity.kt # Native camera/audio capture via MediaCodec
test/
├── moq/protocol/ # Protocol serialization tests
│ ├── wire_format_test.dart
│ ├── control_messages_test.dart
│ ├── control_messages_v16_test.dart
│ ├── data_messages_test.dart
│ └── data_parser_test.dart
├── moq/catalog/ # Catalog and timeline tests
│ ├── moq_catalog_test.dart
│ ├── moq_catalog_subscriber_test.dart
│ └── moq_timeline_test.dart
├── moq/client/ # Client tests
│ ├── client_integration_test.dart
│ ├── mock_transport.dart
│ ├── live_interop_test.dart # Gated: MOQ_LIVE_INTEROP=1
│ └── relay_datagram_test.dart # Gated: MOQ_DATAGRAM_TEST=1
├── moq/publisher/ # Publisher tests
│ └── msf_cmsf_publisher_test.dart
└── moq/media/ # Media pipeline tests
└── streaming_playback_test.dart
The native/moq_quic/ directory contains a Rust library built on Quinn that provides QUIC and WebTransport connectivity via FFI. It must be compiled before the app can establish connections.
- Rust toolchain - Install from rustup.rs
- Cargo - Installed automatically with Rust
- Android NDK - Required for Android cross-compilation (installed via Android Studio)
- libmpv-dev (optional) - Enables the embedded native media player feature; the build auto-detects its presence
The Rust library compiles automatically as part of the Flutter build on all supported platforms:
| Platform | Build system | Trigger |
|---|---|---|
| Linux | CMake custom command in linux/CMakeLists.txt |
flutter build linux / flutter run |
| macOS | Xcode shell build phase ("Copy Native QUIC Library") | flutter build macos |
| Windows | CMake custom command in windows/CMakeLists.txt |
flutter build windows |
| Android | Gradle buildRustLibs task in android/app/build.gradle.kts |
flutter build apk |
| iOS | Not yet automated | Requires manual build (see below) |
On macOS, the build produces a universal binary (arm64 + x86_64) via lipo. On Android, it cross-compiles for all four ABIs (arm64-v8a, armeabi-v7a, x86, x86_64). The media-player feature (libmpv integration) is auto-detected and enabled when libmpv is found on the system.
If cargo is not installed, the build prints a warning and continues without the native library. The app falls back to a stub transport that cannot make real connections.
For development or when you need to rebuild the native library independently:
# Linux / macOS (creates platform-appropriate .so or .dylib)
scripts/build_native.sh
# Windows (creates moq_quic.dll)
scripts/build_native.bat
# Direct cargo build (Linux example)
cd native/moq_quic && cargo build --release
# macOS universal binary
cd native/moq_quic
cargo build --release --target aarch64-apple-darwin
cargo build --release --target x86_64-apple-darwin
lipo -create \
target/aarch64-apple-darwin/release/libmoq_quic.dylib \
target/x86_64-apple-darwin/release/libmoq_quic.dylib \
-output target/release/libmoq_quic.dylib| Platform | Library | Path |
|---|---|---|
| Linux | libmoq_quic.so |
native/moq_quic/target/release/ |
| macOS | libmoq_quic.dylib |
native/moq_quic/target/release/ (universal) |
| Windows | moq_quic.dll |
native/moq_quic/target/release/ |
| Android | libmoq_quic.so |
native/moq_quic/target/{abi}/release/ (per ABI) |
This implementation follows draft-ietf-moq-transport-14:
CLIENT_SETUP(0x20) /SERVER_SETUP(0x21) - Version negotiationSUBSCRIBE(0x3) - Subscribe to a trackSUBSCRIBE_OK(0x4) /SUBSCRIBE_ERROR(0x5) - Subscribe responsesSUBSCRIBE_UPDATE(0x2) - Update existing subscriptionUNSUBSCRIBE(0xA) - Unsubscribe from a trackGOAWAY(0x10) - Session terminationPUBLISH_DONE(0xB) - Publish completion
FETCH(0x16) /FETCH_OK(0x18) /FETCH_ERROR(0x19) /FETCH_CANCEL(0x17)PUBLISH(0x1D) /PUBLISH_OK(0x1E) /PUBLISH_ERROR(0x1F)MAX_REQUEST_ID(0x15)REQUESTS_BLOCKED(0x1A)TRACK_STATUS(0xD) /TRACK_STATUS_OK(0xE) /TRACK_STATUS_ERROR(0xF)SUBSCRIBE_NAMESPACE(0x11) /SUBSCRIBE_NAMESPACE_OK(0x12) /SUBSCRIBE_NAMESPACE_ERROR(0x13)UNSUBSCRIBE_NAMESPACE(0x14)PUBLISH_NAMESPACE(0x6) /PUBLISH_NAMESPACE_OK(0x7) /PUBLISH_NAMESPACE_ERROR(0x8)PUBLISH_NAMESPACE_DONE(0x9) /PUBLISH_NAMESPACE_CANCEL(0xC)
OBJECT_DATAGRAM(0x00) - Object with status indicatorsSUBGROUP_HEADER(0x10) - Subgroup metadataFETCH_HEADER(0x05) - Fetch context header
- Objects: Addressable units of media data
- Groups: Temporal sequences of objects (join points)
- Subgroups: Subdivisions within groups
- Tracks: Collections of groups identified by namespace and name
- Location: {Group ID, Object ID} tuple
- Status: normal, doesNotExist, endOfGroup, endOfTrack
- Filter Types: Largest Object, Next Group Start, Absolute Start, Absolute Range
- Group Order: Ascending, Descending, or Publisher's preference
- Priorities: Subscriber and publisher priority (0-255)
- Forward State: Control whether objects are forwarded
When subscribing to an active stream, the player handles the case where it joins mid-group (missing the keyframe at objectId=0):
- Detection: Automatically detects if first video object has objectId != 0
- Skip Logic: Discards frames from the partial group (P-frames without preceding keyframe)
- Recovery: Waits for next group boundary with objectId=0 to start playback
- Decoder Reset: Resets decoder state when valid starting point is found
This ensures clean playback startup even when relays don't properly implement nextGroupStart filter type.
- Varint Encoding: Variable-length integer encoding (32-bit and 64-bit)
- Tuple Encoding: Array of byte arrays
- Location Encoding: Group and object identifiers
- Message Framing: Type (varint) + Length (16-bit) + Payload format
- SERVER_SETUP response handling with timeout
- Server parameter processing (max_subscribe_id, max_track_alias, supported_versions)
- Track alias mapping
- Request ID handling (even for client, odd for server)
- Connection lifecycle management
- flutter_riverpod: State management
- ffi: Platform-specific FFI bindings for QUIC
- media_kit: Video/audio playback
- media_kit_video: Video widget
- media_kit_libs_video: Native video libraries
- opus_dart / opus_flutter: Native Opus codec via FFI
- go_router: Multi-screen navigation
- shared_preferences: Settings persistence
- camera: Camera access (Flutter plugin)
- audio_streamer: Mobile audio capture
- logger: Logging
- fixnum: 64-bit integer support
- async: Async utilities
- uuid: UUID generation
- permission_handler: Runtime permissions
- Flutter SDK (3.41.2 or higher, see .fvmrc)
- Rust toolchain (for native QUIC library compilation)
- Android/iOS/Desktop build tools
For video playback and capture support on Linux:
# Ubuntu/Debian
sudo apt update && sudo apt install libmpv-dev mpv ffmpeg pulseaudio-utils v4l-utils
# Fedora
sudo dnf install mpv-devel ffmpeg pulseaudio-utils v4l-utils
# Arch Linux
sudo pacman -S mpv ffmpeg libpulse v4l-utilsRequired for media capture:
ffmpeg- Video capture from V4L2 webcams and H.264/Opus encodingpulseaudio-utils- Audio capture viaparec(PulseAudio)v4l-utils- Webcam device enumeration
For video playback or capture on iOS/macOS:
# Install Homebrew if not already installed
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# Install mpv for media playback support
brew install mpvThe media_kit package includes bundled native libraries. No additional dependencies required.
flutter pub get# Run all tests
flutter test
# Run specific test files
flutter test test/moq/protocol/wire_format_test.dart
flutter test test/moq/protocol/control_messages_test.dart
flutter test test/moq/protocol/data_messages_test.dart# Run on connected device/emulator
flutter run
# Build for specific platform
flutter build apk # Android
flutter build ios # iOS
flutter build linux # Linux
flutter build macos # macOS
flutter build windows # WindowsBuilding for an iOS device requires a valid Apple code signing certificate and provisioning profile. If you see "No valid code signing certificates were found", follow these steps:
- Open the Xcode workspace:
open ios/Runner.xcworkspace
- Select the Runner project in the navigator, then the Runner target in the project settings.
- Under Signing & Capabilities, check "Automatically manage signing" and select a Development Team (sign in with your Apple ID if needed).
- Ensure the Bundle Identifier is unique (e.g.,
com.moqapp.moqFlutter). - If deploying to a physical device, register it with your Apple Developer Account and trust the development certificate on the device via Settings > General > Device Management.
- Run
flutter build iosagain.
To build for the iOS Simulator without code signing:
flutter build ios --simulatorFor the APK install, just use the generated APK from the build output:
adb install build/app/outputs/flutter-apk/app-release.apkOr if a previous version is already installed:
adb install -r build/app/outputs/flutter-apk/app-release.apkThe application supports publishing live audio and video with platform-specific capture implementations:
| Platform | Implementation | Details |
|---|---|---|
| Linux | FFmpeg + V4L2 | Captures from /dev/video* devices, outputs YUV420P |
| Android | Native MediaCodec | H.264 via platform channel (MainActivity.kt) |
| iOS/macOS | AVFoundation | Native capture via Platform Channels |
| Windows | Media Foundation | Native capture via Platform Channels |
| Platform | Implementation | Details |
|---|---|---|
| Linux | PulseAudio (parec) | Captures via parec command |
| Android | Native AudioRecord | PCM capture via platform channel (MainActivity.kt) |
| iOS/macOS | AVFoundation | Native capture via Platform Channels |
| Windows | Media Foundation | Native capture via Platform Channels |
- Video: H.264 encoding via FFmpeg (baseline profile, ultrafast preset) or native VideoToolbox (macOS/iOS)
- Audio: Opus encoding via native opus_dart/opus_flutter FFI (all platforms), with FFmpeg fallback on Linux
- Android: Native MediaCodec H.264 encoding + AudioRecord capture via platform channel
Catalogs follow MSF (LOC tracks) and CMSF (CMAF tracks) with per-track packaging and flattened selection parameters.
- CMAF/fMP4 (
packaging: "cmaf"): CMSF-compliant fragmented MP4 packaging with Base64initData - LOC (
packaging: "loc"): Raw codec data packaging (H.264 NALUs, Opus frames) - MoQ-MI: Media Interop format per draft-cenzano-moq-media-interop-03
- LOC-based packaging with extension headers for metadata
- Video: H.264 AVCC format with PTS/DTS/duration/wallclock metadata
- Audio: Opus or AAC-LC with PTS/samplerate/channels metadata
- Track naming:
{prefix}audio0,{prefix}video0
- Video Mute: Toggle video track on/off during publishing
- Audio Mute: Toggle audio track on/off during publishing
Draft-14 and draft-16 dual-version implementation with:
- Full message type definitions for all control and data messages
- Complete wire format utilities (varint, tuple, location encoding/decoding)
- Client with SERVER_SETUP response handling and timeout
- Server parameter processing (max_subscribe_id, max_track_alias, supported_versions)
- Subscription management with full filtering support
- Request ID handling (even for client, odd for server)
- Track alias mapping
- Control message serialization/deserialization
- Data message serialization/deserialization
- Video player integration with media_kit
- State management with Riverpod
- Platform-specific media capture (Linux, Android, iOS, macOS, Windows)
- CMAF/fMP4 and MoQ-MI packaging for MoQ publishing
- CMAF publisher with auto-forward publishing mode, subscribe filter enforcement, and PUBLISH_DONE lifecycle
- Init segment published as group 0 object 0 on each media track
- MSF/CMSF catalog (draft-ietf-moq-msf-00 / draft-ietf-moq-cmsf-00) with flattened track params, delta updates (addTracks/removeTracks/cloneTracks), VOD trackDuration, and backward-compatible parsing of legacy catalogformat
- Native Opus encoder (opus_dart/opus_flutter) and VideoToolbox H.264 encoder (macOS/iOS)
- Android native capture pipeline via MediaCodec/AudioRecord platform channel
- Datagram capability detection for QUIC and WebTransport transports
- Media streaming playback pipeline with CMAF/moq-mi decode routing
- Server-side PUBLISH/PUBLISH_OK/PUBLISH_ERROR handling for receiving publish requests
- Publisher-side SUBSCRIBE/SUBSCRIBE_OK/SUBSCRIBE_ERROR handling for relay subscriptions
- GOAWAY message handling with migration URI support
- Complete QUIC FFI bindings via Rust native library (quinn)
- Data stream handling with SUBGROUP_HEADER parser and transport separation
- Namespace discovery with SUBSCRIBE_NAMESPACE/UNSUBSCRIBE_NAMESPACE support
- FETCH client API for past objects (standalone and joining fetches)
- Video/audio mute controls for publishers
- Multi-screen responsive layout
- Comprehensive test coverage for protocol and client
- Wire Format Tests: Varint encoding/decoding (32/64-bit), tuple, location, message parser
- Control Message Tests: Round-trip serialization for all draft-14 control messages
- Draft-16 Control Message Tests: Version-aware message dispatch, new message types
- Data Message Tests: Object datagram, subgroup header, subgroup object
- Data Parser Tests: Incremental parser edge cases, delta-plus-one encoding
- Catalog Tests: JSON parsing, format field (CMSF/LOC), catalog subscription flow, timeline seeking
- Publisher Tests: CMAF publisher behavior, init segment publishing, PUBLISH_DONE lifecycle
- Media Tests: Streaming playback pipeline, decode/playback routing
- Client Integration Tests
- Connection flow (CLIENT_SETUP/SERVER_SETUP handshake, timeout, disconnect)
- Subscription flow (SUBSCRIBE/SUBSCRIBE_OK/SUBSCRIBE_ERROR, unsubscribe)
- FETCH flow (standalone fetch, FETCH_OK/FETCH_ERROR, cancel)
- Namespace operations (announceNamespace, subscribeNamespace, unsubscribeNamespace)
- GOAWAY handling (with and without migration URI)
- Publisher mode (incoming SUBSCRIBE requests, acceptSubscribe, rejectSubscribe, PUBLISH_DONE)
- Data stream handling (openDataStream, writeSubgroupHeader)
- Live Interop Tests (gated:
MOQ_LIVE_INTEROP=1): Draft-14/16 real relay validation - Relay Datagram Tests (gated:
MOQ_DATAGRAM_TEST=1): QUIC and WebTransport datagram capability
The application saves user preferences using Flutter's shared_preferences package. To clear saved preferences, delete the app data or uninstall the app.
- Android clear app data via Settings > Apps > MoQ Flutter > Storage > Clear Data
- Linux clear app data by executing:
rm -rf ~/.local/share/moq_flutter/
- Add automatic reconnection logic (GOAWAY handling exists)
- Add performance benchmarks for serialization
- draft-ietf-moq-transport-16
- draft-ietf-moq-transport-14
- draft-ietf-moq-msf-00 (MSF - MOQT Streaming Format)
- draft-ietf-moq-cmsf-00 (CMSF - CMAF Streaming Format)
- MoQ Working Group
- Dart Language Documentation
- Flutter Documentation
- Publish Sequence
- Subscribe Sequence