Video Streaming Pipeline — Technical Documentation¶
Status: Coming Soon¶
The video streaming card in the Data Pipelines UI is marked as "Coming Soon" (disabled, non-clickable). The route (/blueprints/tools/data-pipelines/streaming) remains accessible for testing. The comingSoon flag is set in PIPELINE_TEMPLATES in DataPipelines.jsx.
Overview¶
GPU-backed video streaming pipeline. Creates a K8s Deployment on GPU nodes that processes video from RTSP cameras, MinIO files, or other sources, and exposes an HLS or WEBRTC endpoint for playback. Resources are auto-allocated based on resolution and FPS, and freed when the stream is stopped.
Architecture¶
Frontend (StreamingPipeline.jsx)
├─ Start stream → POST /streaming/sessions
├─ Polls GET /streaming/sessions/{id} every 3s for status
├─ When running → GET /streaming/sessions/{id}/stream-url
├─ Video player (native <video> element)
├─ FPS chart (recharts LineChart)
└─ Stop stream → POST /streaming/sessions/{id}/stop
Marketplace Backend (streaming_routes.py)
├─ Thin proxy — authenticates user, derives PNU tenant/user UUIDs
├─ POST /sessions → PNU POST /api/v1/pipelines/jobs (job_type=streaming)
├─ GET /sessions → PNU GET /api/v1/pipelines/jobs?job_type=streaming
├─ GET /sessions/{id} → PNU GET /api/v1/pipelines/jobs/{id}
├─ POST /sessions/{id}/stop → PNU POST /api/v1/pipelines/jobs/{id}/cancel
├─ GET /sessions/{id}/stream-url → PNU GET /api/v1/pipelines/jobs/{id}/results
├─ record_streaming_usage(event="start") — called after session creation
└─ record_streaming_usage(event="stop") — called after session stop
PNU Engine (data_processing_service.py)
├─ submit_job() — stores input in MinIO, creates K8s Deployment
├─ _create_streaming_deployment() — GPU-backed Deployment with video-streamer image
├ update_job_status() — polls K8s for deployment status
├─ cancel_job() — deletes K8s Deployment, frees GPU
└─ data_processing_sweeper — scales idle streaming deployments to 0
Key Files¶
| File | Role |
|---|---|
backend/routes/data_pipeline/streaming_routes.py |
5 HTTP proxy endpoints (no WebSocket) + start/stop metering hooks |
backend/routes/data_pipeline/_common.py |
Shared: derive_uuids(), proxy_error() |
backend/main.py |
Router mounted at /api/data-pipeline/streaming |
backend/middleware/auth_middleware.py:60 |
Protects /api/data-pipeline/streaming/sessions |
frontend/src/components/marketplace/tools/data-pipelines/streaming/StreamingPipeline.jsx |
Full UI: video source input, format/resolution/FPS selectors, video player, GPU metrics, FPS chart, past sessions |
frontend/src/api/dataFetching/dataPipelines.js |
createStreamingSession(), getStreamingSession(), listStreamingSessions(), stopStreamingSession(), getStreamUrl() |
PNU: src/syntera_engine/api/v1/data_pipeline/jobs.py |
POST /pipelines/jobs — submit data processing job |
PNU: src/syntera_engine/services/data_pipeline/gpu_job_service.py |
submit_job(), _create_batch_job(), cancel_job(), update_job_status(), finalize |
PNU: src/syntera_engine/services/data_pipeline/resource_calculator.py |
LLM-based estimator — streaming forced to full GPU via clamp constraints |
PNU: src/syntera_engine/services/data_pipeline/sweeper.py |
Scales idle streaming deployments to 0 |
PNU: src/syntera_engine/models/data_pipeline/data_processing.py |
DataProcessingJob model |
API Endpoints¶
POST /api/data-pipeline/streaming/sessions¶
- Auth: JWT cookie
- Request:
{"session_name": "warehouse camera", "video_source": "rtsp://...", "output_format": "hls", "resolution": "720p", "fps": 30, "processing_config": {}} - Process: Proxies to PNU
POST /api/v1/pipelines/jobswithjob_type="streaming",operation="video_stream". PNU creates a K8s Deployment with GPU resources. - Response:
{"id": "job-uuid", "job_type": "streaming", "operation": "video_stream", "status": "queued", "needs_gpu": true, "gpu_count": 1, "gpu_profile": null, "created_at": "..."}
GET /api/data-pipeline/streaming/sessions¶
- Auth: JWT cookie
- Process: Proxies to PNU
GET /api/v1/pipelines/jobs?job_type=streaming - Response:
{"items": [...], "total": N}
GET /api/data-pipeline/streaming/sessions/{session_id}¶
- Auth: JWT cookie
- Process: Proxies to PNU
GET /api/v1/pipelines/jobs/{id} - Response: Full job object with status, progress, GPU info, error_message
POST /api/data-pipeline/streaming/sessions/{session_id}/stop¶
- Auth: JWT cookie
- Process: Proxies to PNU
POST /api/v1/pipelines/jobs/{id}/cancel. Deletes K8s Deployment, frees GPU. - Response:
{"id": "...", "status": "cancelled", "completed_at": "..."}
GET /api/data-pipeline/streaming/sessions/{session_id}/stream-url¶
- Auth: JWT cookie
- Process: Proxies to PNU
GET /api/v1/pipelines/jobs/{id}/results. Returns presigned MinIO URL for HLS playlist or WEBRTC endpoint. - Response:
{"result_url": "https://minio.../output/job-id/result"}
GPU Resource Allocation¶
| Operation | GPU | MIG Profile | CPU | Memory |
|---|---|---|---|---|
video_stream |
Yes (1 GPU) | Full GPU (no MIG) | 8 cores | 32 GB |
Resources are estimated by ResourceCalculator.estimate_resources() (LLM-based)
and force-clamped to full GPU for streaming. The deployment runs the
syntera/video-streamer:latest container image with GPU resources.
K8s Deployment Lifecycle¶
- Create:
DataProcessingService._create_batch_job()creates a K8s Deployment with 1 replica, GPU resources, and env vars (MinIO creds, input URI, output bucket, job config) - Running:
update_job_status()polls K8s, marks job as "running" when deployment is active - Idle:
sweeper.pyscales idle streaming deployments to 0 after inactivity - Stop:
cancel_job()deletes the K8s Deployment and frees GPU resources
Frontend Flow¶
- Start Stream: Enter session name, video source URL, select format/resolution/FPS →
createStreamingSession()→ setsactiveSessionstate - Status updates:
useQuerypollsgetStreamingSession()every 3s while status is queued/submitting/running (SSE is not yet available for streaming sessions — only ETL/cleaning/labeling/training jobs have SSE endpoints) - Stream URL: When status becomes "running", auto-fetches stream URL via
getStreamUrl() - Video Player: Native
<video>element displays the stream once URL is available - Metrics: GPU info (count + profile), target FPS, progress percentage
- FPS Chart: LineChart showing FPS over time (max 60 data points)
- Stop:
stopStreamingSession()→ cancels job, clears state - Past Sessions: Lists previous streaming jobs, click to view details
Container Image¶
The syntera/video-streamer:latest image is responsible for:
- Connecting to the video source (RTSP, file, etc.)
- GPU-accelerated video processing (transcoding, object detection, overlays)
- Serving HLS playlist or WEBRTC endpoint
- Writing output to MinIO for presigned URL access
Environment variables passed to the container:
- MINIO_ENDPOINT, MINIO_ACCESS_KEY, MINIO_SECRET_KEY, MINIO_USE_PATH_STYLE
- INPUT_DATA_URI — MinIO path to input data
- OUTPUT_BUCKET, OUTPUT_KEY — MinIO path for output
- JOB_CONFIG — JSON with session_name, video_source, output_format, resolution, fps, processing_config
- JOB_TYPE, OPERATION
Migration Notes¶
This feature was changed from sensor-oriented WebSocket streaming to video streaming. The old model used bidirectional WebSocket connections for JSON sensor data ingestion with LLM-based transforms. The new model uses GPU-backed K8s Deployments for video processing with HLS/WEBRTC output.
What was removed:¶
- WebSocket proxy endpoint (
/api/data-pipeline/streaming/ws/{session_id}) DestinationConfig,StreamingSessionRequest(sensor-oriented models)transform_instruction,generated_code,source_type: "websocket"- Live data stream panel, test message sender, message metrics
exportStreamingSession()API function
What was added:¶
VideoStreamRequestmodel with video_source, output_format, resolution, fpsGET /sessions/{id}/stream-urlendpoint for retrieving playback URL- Video player, GPU metrics, FPS chart in frontend
getStreamUrl()API function- Proxy to PNU data processing job endpoints instead of sensor streaming endpoints
Metering¶
Video streaming sessions are metered via start/stop events recorded in the marketplace backend.
Recording points:
- create_stream_session() — after PNU creates the job, calls record_streaming_usage(event="start") with GPU/CPU allocation
- stop_stream_session() — after PNU cancels the job, calls record_streaming_usage(event="stop") with started_at, completed_at, and resource values from the PNU response
Cost formula:
duration_hours = (completed_at - started_at) / 3600
total_cost = (gpu_count × STREAMING_GPU_HOURLY_RATE
+ cpu_cores × STREAMING_CPU_HOURLY_RATE) × duration_hours
Streaming uses a higher GPU hourly rate ($2.00 vs $1.50 for data pipelines) because video encoding is GPU-intensive with sustained NVENC/NVDEC utilization. Duration is computed from started_at and completed_at on the PNU job object.
MongoDB collection: data_pipeline_usage — see Metering Overview for the full schema and data flow.