Live Onchain Video Environment
LOVE is a decentralized live streaming platform built on Celestia's data availability layer. It captures video and audio from your webcam/microphone, encodes them using H.264 video compression, multiplexes them into 2MB data chunks (~8 seconds of A/V), and submits them as blobs to the Celestia blockchain. Viewers can then fetch these blobs and play back the stream in real-time with synchronized audio and video.
- Live Streaming: Real-time video capture from webcam with configurable resolution and framerate
- H.264 Video Compression: Efficient video encoding using ffmpeg for optimal streaming
- Audio Support: Synchronized audio capture from microphone (16-bit PCM, configurable sample rate)
- Local Preview: Optional local preview window for monitoring your stream
- On-chain Storage: Stream data is stored as Celestia blobs with automatic gas estimation
- A/V Sync: Timestamp-based synchronization ensures proper audio/video playback
- Background Prefetching: Viewer fetches blobs in background for smooth playback
- Decentralized: No central server - streams go directly to the blockchain
- Censorship Resistant: Once on-chain, streams cannot be removed
- Pluggable Codec: Interface-based design allows swapping encoding implementations
-
Go 1.21+
-
ffmpeg (required for H.264 encoding/decoding)
# macOS brew install ffmpeg # Ubuntu/Debian apt install ffmpeg
-
OpenCV 4.x with GoCV bindings
# macOS brew install opencv # Ubuntu/Debian apt install libopencv-dev
-
Audio libraries (Linux only)
# Ubuntu/Debian (ALSA) apt install libasound2-dev -
Celestia light node running locally (or remote node access)
-
Auth token for Celestia node
git clone https://github.com/vgonkivs/love.git
cd love
make buildcelestia light auth admin --p2p.network <network># Stream with local preview
make stream token=<auth_token>
# Stream with custom settings
make stream token=<auth_token> fps=15 width=640 height=480
# View a stream
make view token=<auth_token> namespace=<hex> start_height=<height>
# Show help
make help# Basic streaming
./love stream -token <auth_token>
# Custom settings
./love stream -width 1920 -height 1080 -fps 30 -bitrate 4M -samplerate 48000 -token <auth_token>
# View a stream
./love view -namespace <namespace_hex> -height <start_height> -token <auth_token>Press ESC to stop streaming or exit viewer.
| Variable | Default | Description |
|---|---|---|
token |
Celestia auth token (required) | |
node |
http://localhost:26658 | Celestia node URL |
camera |
0 | Camera device ID |
width |
1280 | Video width (pixels) |
height |
720 | Video height (pixels) |
fps |
30 | Frames per second |
namespace |
Stream namespace hex (required for view) | |
start_height |
Start block height (required for view) |
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β STREAMING β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Capturer β β
β β 1. Send entrypoint blob (metadata) β β
β β 2. Initialize devices: β β
β β ββββββββββββ β β
β β β Webcam ββββ β β
β β ββββββββββββ β ββββββββββββββββ βββββββββββββββ β β
β β βββββΆβ Encoder βββββΆβ Preview β β β
β β ββββββββββββ β β (H.264) β β Window β β β
β β β Mic ββββ ββββββββββββββββ βββββββββββββββ β β
β β ββββββββββββ β β β
β β βΌ β β
β β 2MB Blobs β β
β β 3. Send stream end blob β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β βΌ β
β ββββββββββββββββββββββββ β
β β Streamer β β
β β - Random namespace β β
β β - Submit to Celestiaβ β
β ββββββββββββ¬ββββββββββββ β
β β β
βββββββββββββββββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββ
β
βΌ
ββββββββββββββββββββββββ
β Celestia Network β
β β
β Blobs stored in β
β namespace at β
β sequential heights β
ββββββββββββ¬ββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββ
β VIEWING β β
βββββββββββββββββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββ€
β βΌ β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Viewer β β
β β β β
β β βββββββββββββββββββ ββββββββββββββββ ββββββββββββββββββββββ β β
β β β Background βββββΆβ Decoder βββββΆβ Display (GoCV) β β β
β β β Blob Fetcher β β (H.264) β ββββββββββββββββββββββ β β
β β β (prefetching) β β β ββββββββββββββββββββββ β β
β β βββββββββββββββββββ β βββββΆβ Audio Player β β β
β β ββββββββββββββββ β (malgo) β β β
β β ββββββββββββββββββββββ β β
β β A/V Sync: Video paced by timestamps, audio plays at sample rate β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
LOVE uses a pluggable codec architecture. The current implementation is H264Encoder/Decoder using ffmpeg, but the interface allows for alternative implementations:
// Encoder encodes video and audio frames for streaming
type Encoder interface {
EncodeVideo(frame gocv.Mat, timestamp time.Duration, sequence uint32) ([]byte, error)
EncodeAudio(samples []byte, timestamp time.Duration, sequence uint32) ([]byte, error)
CreateEntrypoint(sampleRate int, channels int, fps int) []byte
CreateStreamEnd(totalDuration time.Duration, totalFrames uint32) []byte
}
// Decoder decodes multiplexed video and audio frames
type Decoder interface {
Decode(data []byte) (*DecodedFrame, int)
ParseEntrypoint(data []byte) (sampleRate int, channels int, fps int, valid bool)
}Each video/audio frame is prefixed with a header:
βββββββββββββ¬ββββββββββββ¬ββββββββββββββββββ¬βββββββββββββββ
β Marker β Size β Timestamp β Sequence β
β 4 bytes β 4 bytes β 8 bytes β 4 bytes β
βββββββββββββΌββββββββββββΌββββββββββββββββββΌβββββββββββββββ€
β "H264" or β Payload β Nanoseconds β Frame β
β "AUDF" β length β since start β number β
βββββββββββββ΄ββββββββββββ΄ββββββββββββββββββ΄βββββββββββββββ
- H264: H.264 encoded video frame (may contain multiple NAL units: SPS, PPS, IDR, P-frames)
- AUDF: Audio frame (16-bit PCM samples)
Frames are accumulated into 2MB blobs (~8 seconds of A/V at 2Mbps video + 128kbps audio):
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 2MB Blob β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β [Header][H.264 Data][Header][PCM Data][Header][H.264]...β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
The Capturer sends an entrypoint blob first (before camera initialization) with stream metadata:
βββββββββββββ¬ββββββββββββββ¬βββββββββββ¬ββββββββββ¬ββββββββ¬ββββββββ¬βββββββββ
β Marker β Sample Rate β Channels β FPS β Codec β Width β Height β
β 4 bytes β 4 bytes β 1 byte β 1 byte β1 byte β2 bytesβ2 bytes β
βββββββββββββΌββββββββββββββΌβββββββββββΌββββββββββΌββββββββΌββββββββΌβββββββββ€
β "ENTR" β 44100 β 1 β 30 β 1 β 1280 β 720 β
βββββββββββββ΄ββββββββββββββ΄βββββββββββ΄ββββββββββ΄ββββββββ΄ββββββββ΄βββββββββ
Codec: 0 = JPEG (legacy), 1 = H.264
When the stream ends gracefully (ESC or Ctrl+C), the Capturer sends a stream end notification:
βββββββββββββ¬ββββββββββββββββββββββ¬βββββββββββββββ
β Marker β Total Duration β Total Frames β
β 4 bytes β 8 bytes β 4 bytes β
βββββββββββββΌββββββββββββββββββββββΌβββββββββββββββ€
β "ENDS" β Nanoseconds β Count β
βββββββββββββ΄ββββββββββββββββββββββ΄βββββββββββββββ
This allows viewers to distinguish between "stream ended gracefully" vs "stream stopped unexpectedly".
| Option | Default | Description |
|---|---|---|
-camera |
0 | Camera device ID |
-width |
1280 | Video width (pixels) |
-height |
720 | Video height (pixels) |
-fps |
30 | Frames per second |
-bitrate |
2M | H.264 bitrate (e.g., 2M, 4M) |
-samplerate |
44100 | Audio sample rate (Hz) |
-node |
http://localhost:26658 | Celestia node URL |
-token |
Auth token (required) |
| Option | Default | Description |
|---|---|---|
-namespace |
Stream namespace hex (required) | |
-height |
Start block height (required) | |
-node |
http://localhost:26658 | Celestia node URL |
-token |
Auth token (required) |
- Entrypoint: Capturer sends entrypoint blob with stream metadata (sample rate, channels, fps, dimensions, codec)
- Initialize: Capturer opens webcam (GoCV) and microphone (malgo)
- Preview: Frames are displayed in local preview window (optional)
- Encode: Video frames are H.264 encoded via ffmpeg (SPS/PPS/IDR combined), audio is 16-bit PCM
- Multiplex: Frames are tagged with H264/AUDF markers and timestamps
- Chunk: Data is accumulated into 2MB buffers inside Capturer (~8 seconds of A/V)
- Submit: Blobs are submitted to Celestia via Streamer with automatic gas estimation
- Stream End: When stopping gracefully, Capturer sends stream end blob with total duration and frame count
- Connect: Viewer connects to Celestia node
- Find Entrypoint: Locate the ENTR blob with stream parameters and codec type
- Create Decoder: Initialize H.264 decoder based on codec identifier
- Background Fetch: Goroutine prefetches blobs at sequential block heights into a buffered channel
- Decode: Parse frame headers, decode H.264 video via ffmpeg, extract PCM audio
- A/V Sync: Video is paced by timestamps, audio plays at native sample rate through malgo
- Display: Show video in window, play audio through speakers
- SPS/PPS Caching: Decoder caches parameter sets for mid-stream joining
love/
βββ main.go # CLI entry point
βββ Makefile # Build and run commands
βββ cmd/
β βββ chaintest/ # H.264 encode/decode chain test app
β βββ main.go
βββ lib/
β βββ capture/ # Video + audio capture with embedded encoder
β β βββ capture.go # Capturer implementation
β β βββ config.go # Capture configuration
β βββ codec/ # Encoding/decoding interfaces and implementations
β β βββ interface.go # Encoder/Decoder interfaces
β β βββ jpeg.go # JPEGCodec implementation (legacy)
β β βββ h264_encoder.go # H.264 encoder using ffmpeg
β β βββ h264_decoder.go # H.264 decoder using ffmpeg
β β βββ codec.go # Shared constants and helpers
β β βββ decoder.go # Frame decoding utilities
β βββ streamer/ # Celestia blob submission
β β βββ streamer.go # Streamer implementation
β β βββ config.go # Streamer configuration
β βββ viewer/ # Blob fetching + playback with embedded decoder
β βββ viewer.go # Viewer implementation (background fetcher + A/V sync)
β βββ config.go # Viewer configuration
- Ensure ffmpeg is installed:
ffmpeg -version - Check if OpenCV/GoCV is properly installed
- Linux: Install ALSA dev libraries:
apt install libasound2-dev - Check microphone permissions
- This happens when joining mid-stream before a keyframe
- Wait for the next keyframe or restart from an earlier height
- Ensure you're using a fresh recording (old recordings may have sync issues)
- The viewer uses timestamp-based sync - video paced by timestamps, audio at native rate
MIT
Go live with LOVE