A high-performance embedded Linux web server written in Go that provides real-time image streaming capabilities for monitoring computer vision video output from BrightSign players using Machine Vision BrightSign Model Packages (BSMP) such as the Gaze Detection extension.
This is useful to "see" what the output of the machine vision is with the bounding boxes drawn.
The bs-image-stream-server continuously monitors a local image file and serves it via HTTP at 30 FPS. It specifically watches /tmp/output.jpg since that is where the BSMP files write their output.
This is intended for development and testing purposes only. If you want to include this binary in a BSMP or an extension, you should look at the BrightSign Extension Template.
The server operates using a simple but effective architecture:
- File Monitoring: Watches a specified image file (e.g.,
/tmp/output.jpg) using 33ms intervals (30 FPS) - Change Detection: Uses file modification time comparison to detect when the image updates
- Memory Caching: Stores the current image in memory with ETag support for efficient serving
- Multi-Format Streaming: Serves the image through multiple endpoints:
- Web Interface (
/): HTML page with JavaScript-based 30 FPS refresh - MJPEG Stream (
/video): Direct multipart MJPEG stream for browsers and ffmpeg - Raw Image (
/image): Direct JPEG access with HTTP caching headers
- Web Interface (
- Zero Dependencies: Single binary with all assets embedded - no external files needed
-
Start the server with your image file path:
./bs-image-stream-server -file /path/to/your/image.jpg
-
View the live stream in your browser:
- Navigate to
http://<player>:8080/orhttp://<player>:8080/video - The image will automatically refresh at 30 FPS
- Navigate to
-
Direct image access for integration:
- Access
http://<player>:8080/imageto get the raw JPEG data - Supports ETag headers for efficient caching
- Access
- Monitor BrightSign player output: Point to the screenshot file your player generates
- Security camera feeds: Monitor JPEG files updated by IP cameras
- Image processing pipelines: View the output of computer vision applications
- Remote diagnostics: Check display content from anywhere on your network
- High-Performance Streaming: 30 FPS image updates with minimal CPU overhead
- Embedded System Optimized: Designed for resource-constrained Linux environments
- Professional UI: BrightSign-branded web interface with modern gradient styling
- Efficient Change Detection: Uses file modification time instead of content hashing
- Zero Dependencies: Built with Go standard library only, with assets embedded in binary
- Cross-Platform: Supports ARM, ARM64, and x86_64 architectures
- ETag Support: Bandwidth optimization with HTTP 304 Not Modified responses
- Graceful Shutdown: Clean shutdown with signal handling (SIGINT/SIGTERM)
- Web Server: HTTP server serving static HTML and real-time image endpoints
- File Monitor: Efficient 30 FPS polling with modification time detection
- Image Cache: Thread-safe memory caching with ETag support
- Professional Interface: Responsive web UI with BrightSign branding
- Go 1.21.5 or later
- Linux, macOS, or Windows with WSL
- A source that generates images to
/tmp/output.jpg - Optional: bscp for BrightSign deployment
# Clone the repository
git clone <repository-url>
cd bs-image-stream-server
# Build the application
make build
# Run with default settings
make run
# Run with debug logging
make run-debug
# Run with custom settings
make run-custom # runs on port 8080 with custom file path# Show all available make targets
make help
# Build and run in one command
make run
# Build and install to BrightSign player
make install PLAYER=<player-hostname>
# Development cycle (format, vet, test, build)
make dev-cycle
# Clean all build artifacts
make clean
# Initialize go modules
make depsStart the server and view the stream in your browser:
# Start monitoring an image file
./bs-image-stream-server -file /path/to/image.jpg -port 8080
# View in browser with web interface
open http://<player>:8080/
# View raw video stream (no HTML wrapper)
open http://<player>:8080/videoThe /video endpoint provides an MJPEG stream that ffmpeg can record:
# Basic recording (copies stream as-is)
ffmpeg -i http://<player>:8080/video -c copy recording.mpeg
# Record for specific duration (60 seconds)
ffmpeg -i http://<player>:8080/video -t 60 -c copy recording.mpeg
# Convert to MP4 while recording
ffmpeg -i http://<player>:8080/video -c:v libx264 -r 30 recording.mp4
# High quality MP4 recording
ffmpeg -i http://<player>:8080/video -c:v libx264 -crf 18 -r 30 high_quality.mp4
# Record with custom frame rate
ffmpeg -i http://<player>:8080/video -r 25 -c:v libx264 output.mp4Use the video stream with streaming platforms:
# Stream to YouTube Live (requires stream key)
ffmpeg -i http://<player>:8080/video -c:v libx264 -b:v 2500k -r 30 \
-f flv rtmp://a.rtmp.youtube.com/live2/YOUR_STREAM_KEY
# Stream to Twitch (requires stream key)
ffmpeg -i http://<player>:8080/video -c:v libx264 -b:v 2500k -r 30 \
-f flv rtmp://live.twitch.tv/app/YOUR_STREAM_KEY
# Re-stream to local RTMP server
ffmpeg -i http://<player>:8080/video -c:v libx264 -f flv rtmp://<rtmp-server>/live/streamEmbed or integrate the stream in applications:
# View in VLC media player
vlc http://<player>:8080/video
# Use with OBS Studio
# Add "Media Source" → Input: http://<player>:8080/video
# Embed in HTML page
echo '<img src="http://<player>:8080/video" alt="Live Stream">' > viewer.html
# Use with curl for testing
curl -N http://<player>:8080/video > stream_test.mjpeg# Check server health
curl http://<player>:8080/health
# Get current image directly
curl http://<player>:8080/image > current_frame.jpg
Set up automated recording with rotation:
# Start the server if testing development
./bs-image-stream-server -file /tmp/output.jpg &
# Record 1-hour segments with timestamps
while true; do
timestamp=$(date +%Y%m%d_%H%M%S)
ffmpeg -i http://<player>:8080/video -t 3600 -c copy "recording_${timestamp}.mpeg"
sleep 10 # Brief pause between recordings
donebs-image-stream-server/
├── CLAUDE.md # Claude Code guidance and implementation plan
├── Makefile # Build automation and tasks
├── README.md # This file
├── go.mod # Go module definition
├── main.go # Application entry point
├── internal/
│ ├── cache/
│ │ ├── image_cache.go # Thread-safe image caching
│ │ └── image_cache_test.go # Cache unit tests
│ ├── monitor/
│ │ ├── file_monitor.go # 30 FPS file monitoring
│ │ └── file_monitor_test.go # Monitor unit tests
│ ├── server/
│ │ ├── server.go # HTTP server setup
│ │ ├── handlers.go # HTTP request handlers
│ │ ├── handlers_test.go # Handler unit tests
│ │ └── static/
│ │ └── index.html # BrightSign-branded web interface
│ └── testutil/
│ └── image_generator.go # Test image generation utilities
├── integration_test.go # End-to-end integration tests
├── load_test.go # Performance load testing
└── test-plan.md # Comprehensive test plan
| Endpoint | Method | Description | Use Case |
|---|---|---|---|
/ |
GET | HTML viewing interface with BrightSign branding | General monitoring and viewing with web interface |
/video |
GET | Multipart MJPEG stream | Browser viewing and ffmpeg recording |
/provides a branded web interface with JavaScript-based 30 FPS refresh/videoprovides an MJPEG stream that works with both browsers and recording tools
| Endpoint | Method | Description | Use Case |
|---|---|---|---|
/image |
GET | Raw JPEG image with caching headers | Direct image access for custom applications |
/health |
GET | JSON health status | Monitoring and load balancer health checks |
/images/* |
GET | Static assets (logos, etc.) | Internal use by the web interface |
- Purpose: Human-friendly web interface for viewing the live image stream
- Features:
- Auto-refreshes at 30 FPS using JavaScript
- Clean, branded interface with purple frame
- BrightSign logo and professional styling
- Works in any modern browser
- When to use: When you need a polished interface for monitoring
- Purpose: Live video streaming compatible with browsers and recording tools
- Features:
- Native browser video streaming at 30 FPS
- Direct ffmpeg recording compatibility (confirmed working)
- No HTML wrapper or JavaScript required
- Lower bandwidth than JavaScript refresh approach
- Works with VLC, OBS, and other video software
- When to use:
- Browser viewing without web interface
- Video recording with ffmpeg (
ffmpeg -i http://<player>/video -c copy output.mpeg) - Embedding in other applications
- Streaming to platforms (YouTube, Twitch, etc.)
- Purpose: Programmatic access to the current image
- Features:
- Returns raw JPEG data
- Includes ETag header for efficient caching
- Returns 304 Not Modified if image hasn't changed
- Ideal for custom applications or embedding
- When to use:
- Building custom viewing applications
- Integrating with other systems
- Creating image processing pipelines
- When you need efficient bandwidth usage with ETag support
- Purpose: Monitor server status
- Response format:
{ "status": "ok", "timestamp": "2024-01-15T10:30:00Z" } - Status values:
"ok"- Server is running and has image data"no_image"- Server is running but no image is available yet
- When to use: Load balancer health checks, monitoring systems
./bs-image-stream-server [options]
Options:
-port int
HTTP server port (default 8080)
-file string
Path to image file to monitor (default "/tmp/output.jpg")
-debug
Enable debug loggingAll build targets automatically disable CGO for static binary compilation.
make build-embedded# Linux x86_64
make build-linux
# ARM (Raspberry Pi 3, 32-bit)
make build-arm
# ARM64 (Raspberry Pi 4, 64-bit)
make build-arm64
# Note: BrightSign/Rockchip ARM64 player is built automatically with 'make build'All binaries are placed in the cmd/ directory:
cmd/image-stream-server-amd64- Linux x86_64cmd/image-stream-server-arm- ARM 32-bitcmd/image-stream-server-arm64- ARM 64-bitcmd/image-stream-server-player- BrightSign player
Deploy directly to BrightSign players using the make install target:
# Install to default player hostname 'brightsign'
make install
# Install to specific player
make install PLAYER=my-player-name
make install PLAYER=192.168.1.100
# Set default player in environment
export PLAYER=my-brightsign-device
make installRequirements:
- bscp must be installed
- Network access to the BrightSign player
- Player must have SSH enabled
Installation process:
- Builds the ARM64 player binary
- Copies binary to player's
/tmp/bs-image-stream-server - Provides instructions for starting the server on the player
# Run all tests
make test
# Run with coverage
make test-coverage
# Run integration tests only
make test ARGS="./integration_test.go"
# Run load tests only
make test ARGS="./load_test.go"# Format code
make fmt
# Vet code
make vet
# Run linter (requires golangci-lint)
make lint
# Full development cycle
make dev-cycle# Simple load test against running server
make load-testUse the built-in deployment system:
# Quick deployment to BrightSign player
make install PLAYER=<player-hostname>
# Then SSH to the player to start the service
ssh brightsign@<player-hostname>
/tmp/bs-image-stream-server -file /tmp/screenshot.jpg -port 8080Create /etc/systemd/system/bs-image-stream-server.service:
[Unit]
Description=BrightSign Image Stream Server
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/opt/bs-image-stream-server
ExecStart=/opt/bs-image-stream-server/bs-image-stream-server -port 8080 -file /tmp/output.jpg
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.targetEnable and start:
sudo systemctl enable bs-image-stream-server
sudo systemctl start bs-image-stream-serverThe application compiles to a single self-contained static binary with no external dependencies or files:
# Build static binary for deployment
make build-linux
# Copy single binary to target system - no additional files needed
scp cmd/image-stream-server-amd64 user@target:/opt/bs-image-stream-server/
# The binary includes all assets (HTML, CSS, logo) embedded
# No need to copy additional filesThe server is optimized for embedded systems with:
- Memory efficiency: Reuses buffers, minimizes allocations
- CPU efficiency: Only reads files when modification time changes
- Network efficiency: ETag support reduces bandwidth usage
- Concurrent handling: Thread-safe operations throughout
- Digital Signage Monitoring: Real-time view of display output
- Camera Feed Streaming: Live monitoring of security or industrial cameras
- Automated Testing: Visual verification of display content in CI/CD pipelines
- Remote Troubleshooting: Quick visual assessment of remote display issues
- Machine Vision: Monitor output from image processing applications
- Video Recording: Capture live streams for analysis or archival
The /video endpoint works directly with ffmpeg without requiring format parameters:
# Basic recording (confirmed working)
ffmpeg -i http://<player>:8080/video -c copy recording.mpeg
# If stream format issues occur, try re-encoding
ffmpeg -i http://<player>:8080/video -c:v libx264 -r 30 recording.mp4
# For longer recordings, add duration limit
ffmpeg -i http://<player>:8080/video -t 3600 -c copy recording.mpeg
# If connection issues occur, increase buffer size
ffmpeg -i http://<player>:8080/video -buffer_size 32768 -c copy recording.aviExpected behavior: ffmpeg will detect the stream as mpjpeg format and achieve 25-30 FPS recording. The "Packet corrupt" warning at the end is normal when the stream ends.
ffmpeg should show output similar to:
Input #0, mpjpeg, from 'http://<player>:8080/video':
Duration: N/A, bitrate: N/A
Stream #0:0: Video: mjpeg (Baseline), yuvj420p, 640x480, 25 tbr, 25 tbn
- Web Interface (
/): Works in all modern browsers - Video Stream (
/video): Works in browsers that support multipart MJPEG - Direct Image (
/image): Universal compatibility with HTTP caching
- No image displayed: Check that the source file exists and is being updated
- Stream won't start: Verify the file path and permissions
- ffmpeg connection refused: Ensure the server is running and accessible
- Low frame rate: Source file may not be updating at 30 FPS
This project is released under the terms of the Apache 2.0 License.