PRIVATE ALPHA

Complete configuration

Start from a maximal module composition or inspect every field accepted by the current binary.

How to use this page

The maximal composition is a practical deployment template. It enables every module that can run together and leaves configured outbound push targets empty because cascade input and static push-all targets cannot share one node yet.

The field reference below names every accepted configuration field. Commented alternatives are mutually exclusive with the active line beside them. Remove modules and optional values that the deployment does not need.

Private alpha: match this page to the binary version you deploy. Unknown fields fail startup, and configuration fields may still change before a stable release.

Maximal composition

This template combines SRT push, managed pull, demand-driven cascade input, HLS, LL-HLS, WebRTC, local thumbnails, recording, S3 publication, cascade output, push control, transcode repair and webhooks. Set every referenced environment value and provide the external services before starting the process.

{
  "server": {
    "operational": {"host": "0.0.0.0", "port": 9091},
    "shutdownTimeout": "10s"
  },
  "input": {
    "mediaTimeout": "15s",
    "srt": {
      "push": {
        "enabled": true,
        "listener": {
          "host": "0.0.0.0",
          "port": 6001,
          "passphrase": "${INGEST_SRT_PASSPHRASE}"
        },
        "peerIdleTimeout": "5s"
      },
      "pull": {
        "api": {"enabled": true},
        "static": [{
          "streamId": "preview",
          "source": "source.internal:7001",
          "srtStreamId": "preview",
          "passphrase": "${SOURCE_SRT_PASSPHRASE}"
        }],
        "maxActivePulls": 8,
        "maxTombstones": 100
      },
      "cascade": {
        "enabled": true,
        "staticEndpoint": "upstream.internal:6102",
        "passphrase": "${CASCADE_UPSTREAM_PASSPHRASE}",
        "viewerIdleTimeout": "1m",
        "maxOnDemandStreams": 64
      }
    }
  },
  "output": {
    "root": "/var/lib/dispergo",
    "lowWaterMarkBytes": 5368709120,
    "http": {
      "listener": {
        "host": "0.0.0.0",
        "port": 8080,
        "maxConnections": 2048
      },
      "hls": {"enabled": true, "segmentDuration": "6s"},
      "llhls": {"enabled": true, "windowDuration": "5m"},
      "webrtc": {
        "enabled": true,
        "network": {
          "udpPort": 50000,
          "tcpPort": 50001,
          "ipFamilies": ["ipv4"],
          "candidateMode": "direct"
        },
        "security": {
          "mode": "bearer",
          "credentials": [{
            "principal": "browser-viewers",
            "token": "${WHEP_TOKEN}",
            "streams": ["*"]
          }],
          "allowedOrigins": ["https://player.example.com"]
        },
        "ice": {
          "urls": ["turns:turn.example.com:5349?transport=tcp"],
          "turnSharedSecret": "${TURN_SHARED_SECRET}",
          "internetReady": true
        },
        "timeouts": {"bootstrap": "2s"},
        "admission": {
          "maxSessions": 256,
          "maxSessionsPerStream": 64,
          "maxBootstrap": 64,
          "maxBootstrapPerStream": 16
        },
        "rates": {
          "bootstrapGlobal": {"perSecond": 64, "burst": 128},
          "bootstrapScoped": {"perSecond": 4, "burst": 8}
        },
        "tracing": {"sampleRate": 0.005},
        "audio": {"ffmpegPath": "ffmpeg", "maxWorkers": 4}
      }
    },
    "srt": {
      "cascade": {
        "enabled": true,
        "listener": {
          "host": "0.0.0.0",
          "port": 6002,
          "passphrase": "${CASCADE_DOWNSTREAM_PASSPHRASE}"
        },
        "registry": {
          "redisAddress": "redis.internal:6379",
          "advertiseEndpoint": "node-a.internal:6002",
          "keyPrefix": "dispergo:provide:"
        }
      },
      "push": {
        "api": {"enabled": true},
        "maxActivePushes": 8,
        "maxTombstones": 100
      }
    },
    "thumbnails": {
      "enabled": true,
      "publish": true,
      "interval": "10s",
      "resize": {"width": 400, "height": 225},
      "sessionMaxBytes": 1073741824,
      "sessionMaxArtifacts": 10000,
      "retention": "0s",
      "ffmpegPath": "ffmpeg",
      "maxWorkers": 4
    },
    "recording": {
      "enabled": true,
      "chunkDuration": "6s",
      "retention": "0s",
      "publish": true
    },
    "s3": {
      "enabled": true,
      "bucket": "${S3_BUCKET}",
      "prefix": "media",
      "region": "${AWS_REGION}",
      "maxConcurrentUploads": 64,
      "maxConcurrentThumbnailUploads": 8
    }
  },
  "transcode": {
    "enabled": true,
    "encoderProfile": "cpu",
    "ffmpegPath": "ffmpeg",
    "maxWorkers": 4,
    "queueSize": 0,
    "policy": {
      "defaultMode": "auto",
      "streams": {"tenant:legacy": "always"}
    }
  },
  "webhook": {
    "enabled": true,
    "url": "${WEBHOOK_URL}",
    "authToken": "${WEBHOOK_TOKEN}",
    "requestTimeout": "5s",
    "maxAttempts": 5,
    "queueSize": 256,
    "maxConcurrentDeliveries": 4,
    "shutdownFlushTimeout": "2s"
  }
}

All fields

This JSONC document makes every compatible field explicit. Values match safe example bounds or current defaults. The commented alternatives name fields that cannot be active beside the selected form. The full output.s3 object is also accepted as the legacy output.recording.s3 alias, but the two paths cannot coexist. That alias is recording-only and cannot enable thumbnail publication.

{
  "server": {
    "operational": {
      "host": "0.0.0.0",
      "port": 9091
    },
    "shutdownTimeout": "10s"
  },
  "input": {
    "mediaTimeout": "15s",
    "srt": {
      "push": {
        "enabled": true,
        "listener": {
          "host": "0.0.0.0",
          "port": 6001,
          "passphrase": "${INGEST_SRT_PASSPHRASE}"
          // Alternative after removing passphrase:
          // "allowAnonymous": true
        },
        "peerIdleTimeout": "5s"
      },
      "pull": {
        "defaults": {
          "connectionTimeout": "5s",
          "backoffBase": "1s",
          "backoffMultiplier": 2,
          "backoffCap": "1m",
          "retryDeadline": "5m"
        },
        "api": {
          "enabled": true
        },
        "static": [{
          "streamId": "preview",
          "source": "source.internal:7001",
          "srtStreamId": "preview",
          "passphrase": "${SOURCE_SRT_PASSPHRASE}",
          "connectionTimeout": "5s",
          "backoffBase": "2s",
          "backoffMultiplier": 2,
          "backoffCap": "1m",
          "retryDeadline": "5m"
        }],
        "maxActivePulls": 8,
        "maxTombstones": 100
      },
      "cascade": {
        "enabled": true,
        "staticEndpoint": "upstream.internal:6102",
        "passphrase": "${CASCADE_UPSTREAM_PASSPHRASE}",
        "viewerIdleTimeout": "1m",
        "maxOnDemandStreams": 64
        // Alternative to staticEndpoint:
        // "registry": {
        //   "redisAddress": "redis.internal:6379",
        //   "keyPrefix": "dispergo:resolve:"
        // }
      }
    }
  },
  "output": {
    "root": "/var/lib/dispergo",
    "lowWaterMarkBytes": 5368709120,
    "http": {
      "listener": {
        "host": "0.0.0.0",
        "port": 8080,
        "maxConnections": 2048
      },
      "hls": {
        "enabled": true,
        "segmentDuration": "6s",
        "maxSegmentBytes": 67108864,
        "maxSegments": 7,
        "minSegments": 3,
        "maxTotalBytes": 1073741824,
        "bandwidth": 10000000
      },
      "llhls": {
        "enabled": true,
        "segmentTargetDuration": "1s",
        "segmentHardDuration": "2s",
        "partTargetDuration": "200ms",
        "partHardDuration": "400ms",
        "partPublishedTargetDuration": "340ms",
        "partHoldBack": "680ms",
        "windowDuration": "5m",
        "maxSegments": 300,
        "minSegments": 3,
        "partWindowDuration": "15s",
        "maxSegmentBytes": 67108864,
        "maxPartBytes": 8388608,
        "maxTotalBytes": 1073741824,
        "bandwidth": 10000000,
        "blockingReloadMaxWait": "8s",
        "initialPlaylistMaxWait": "8s"
      },
      "webrtc": {
        "enabled": true,
        "network": {
          "udpPort": 50000,
          "tcpPort": 50001,
          "udpBindIPs": ["10.0.0.10"],
          "tcpBindIPs": ["10.0.0.10"],
          "ipFamilies": ["ipv4"],
          "interfaces": ["eth0"],
          "localCIDRs": ["10.0.0.0/24"],
          "candidateMode": "rewrite",
          "addressRewrites": [{
            "localCIDR": "10.0.0.0/24",
            "externalIP": "198.51.100.10"
          }],
          "disconnectedTimeout": "5s",
          "failedTimeout": "25s",
          "keepAliveInterval": "5s",
          "candidateGatherTimeout": "3s",
          "tcpFirstStunTimeout": "5s",
          "tcpUnknownUfragTimeout": "30s",
          "viewerDrainTimeout": "5s",
          "certificateRotationLead": "24h"
        },
        "security": {
          "mode": "bearer",
          "credentials": [{
            "principal": "browser-viewers",
            "token": "${WHEP_TOKEN}",
            "streams": ["*"]
          }],
          "allowedOrigins": ["https://player.example.com"],
          "trustedProxyCIDRs": ["10.0.0.0/8"]
        },
        "timeouts": {
          "setup": "10s",
          "bootstrap": "2s",
          "body": "2s",
          "patch": "2s",
          "delete": "2s",
          "idle": "30s",
          "maxLifetime": "6h"
        },
        "admission": {
          "maxSessions": 256,
          "maxSessionsPerStream": 64,
          "maxSessionsPerPrincipal": 32,
          "maxSessionsPerAnonymous": 8,
          "maxHalfOpen": 64,
          "maxHalfOpenPerStream": 16,
          "maxHalfOpenPerPrincipal": 8,
          "maxHalfOpenPerAnonymous": 4,
          "maxBootstrap": 64,
          "maxBootstrapPerStream": 16,
          "maxBootstrapPerPrincipal": 8,
          "maxBootstrapPerAnonymous": 4
        },
        "rates": {
          "postGlobal": {"perSecond": 64, "burst": 128},
          "postScoped": {"perSecond": 4, "burst": 8},
          "bootstrapGlobal": {"perSecond": 64, "burst": 128},
          "bootstrapScoped": {"perSecond": 4, "burst": 8},
          "patchGlobal": {"perSecond": 512, "burst": 1024},
          "patchScoped": {"perSecond": 40, "burst": 80},
          "patchSession": {"perSecond": 10, "burst": 20},
          "deleteGlobal": {"perSecond": 256, "burst": 512},
          "deleteScoped": {"perSecond": 20, "burst": 40},
          "maxKeyedBuckets": 4096,
          "bucketIdle": "10m"
        },
        "ice": {
          "urls": ["turns:turn.example.com:5349?transport=tcp"],
          "turnSharedSecret": "${TURN_SHARED_SECRET}",
          "credentialTTL": "7h",
          "allowedClockSkew": "30s",
          "internetReady": true
        },
        "audio": {
          "ffmpegPath": "ffmpeg",
          "maxWorkers": 4,
          "maxStartups": 2,
          "gracePeriod": "10s",
          "startupTimeout": "5s",
          "restartBudget": 3,
          "restartWindow": "1m",
          "backoffBase": "250ms",
          "backoffCap": "5s",
          "maxSubscribers": 256,
          "subscriberFrames": 8,
          "inputQueueSamples": 8,
          "inputQueueBytes": 262144,
          "stallTimeout": "5s"
        },
        "tracing": {
          "sampleRate": 0.005
        }
      }
    },
    "srt": {
      "cascade": {
        "enabled": true,
        "listener": {
          "host": "0.0.0.0",
          "port": 6002,
          "passphrase": "${CASCADE_DOWNSTREAM_PASSPHRASE}"
          // Alternative after removing passphrase:
          // "allowAnonymous": true
        },
        "registry": {
          "redisAddress": "redis.internal:6379",
          "advertiseEndpoint": "node-a.internal:6002",
          "keyPrefix": "dispergo:provide:"
        }
      },
      "push": {
        "defaults": {
          "connectionTimeout": "5s",
          "backoffBase": "1s",
          "backoffMultiplier": 2,
          "backoffCap": "1m",
          "retryDeadline": "5m",
          "payloadSize": 1316,
          "peerLatency": "120ms",
          "receiverLatency": "120ms",
          "sendBufferSize": 1048576,
          "flowControlWindow": 8192
        },
        "api": {
          "enabled": true
        },
        "static": [
          // Remove input.srt.cascade before enabling this target.
          // {
          //   "name": "archive",
          //   "address": "archive.internal:7001",
          //   "srtStreamId": "{streamId}",
          //   "passphrase": "${ARCHIVE_SRT_PASSPHRASE}",
          //   "connectionTimeout": "5s",
          //   "backoffBase": "2s",
          //   "backoffMultiplier": 2,
          //   "backoffCap": "1m",
          //   "retryDeadline": "5m",
          //   "payloadSize": 1316,
          //   "peerLatency": "120ms",
          //   "receiverLatency": "120ms",
          //   "sendBufferSize": 1048576,
          //   "flowControlWindow": 8192
          // }
        ],
        "maxActivePushes": 8,
        "maxTombstones": 100
      }
    },
    "thumbnails": {
      "enabled": true,
      "publish": true,
      "interval": "10s",
      "resize": {
        "width": 400,
        "height": 225
      },
      "sessionMaxBytes": 1073741824,
      "sessionMaxArtifacts": 10000,
      "retention": "0s",
      "ffmpegPath": "ffmpeg",
      "maxWorkers": 4
    },
    "recording": {
      "enabled": true,
      "chunkDuration": "6s",
      "maxChunkBytes": 67108864,
      "sessionMaxBytes": 10737418240,
      "sessionMaxArtifacts": 10000,
      "retention": "0s",
      "publish": true
    },
    "s3": {
      "enabled": true,
      "bucket": "${S3_BUCKET}",
      "prefix": "media",
      "region": "${AWS_REGION}",
      "endpoint": "https://s3.example.net",
      "forcePathStyle": false,
      "requestTimeout": "2m",
      "multipartAttemptTimeout": "5m",
      "multipartThresholdBytes": 134217728,
      "multipartPartSizeBytes": 16777216,
      "maxConcurrentUploads": 64,
      "maxConcurrentThumbnailUploads": 8
    }
  },
  "transcode": {
    "enabled": true,
    "encoderProfile": "cpu",
    "ffmpegPath": "ffmpeg",
    "gracePeriod": "10s",
    "maxWorkers": 4,
    "queueSize": 0,
    "stallTimeout": "5s",
    "noMediaTimeout": "8s",
    "maxRuntime": "12h",
    "restartBudget": 5,
    "restartWindow": "5m",
    "backoffBase": "1s",
    "backoffCap": "1m",
    "sourceBufferBytes": 262144,
    "maxAddressSpaceBytes": 2147483648,
    "maxOpenFiles": 256,
    "policy": {
      "defaultMode": "auto",
      "streams": {
        "tenant:legacy": "always",
        "tenant:trusted": "never"
      }
    }
  },
  "webhook": {
    "enabled": true,
    "url": "${WEBHOOK_URL}",
    "authToken": "${WEBHOOK_TOKEN}",
    "requestTimeout": "5s",
    "maxAttempts": 5,
    "queueSize": 256,
    "maxConcurrentDeliveries": 4,
    "shutdownFlushTimeout": "2s"
  }
}

Composition constraints

  • Use either passphrase or allowAnonymous on one SRT listener, never both.
  • Use either input.srt.cascade.staticEndpoint or its registry, never both.
  • Give cascade input and output registries distinct keyPrefix values when both run in one process.
  • Do not combine cascade input with configured output.srt.push.static targets. The push API remains available.
  • Keep transcode.stallTimeout and transcode.noMediaTimeout below input.mediaTimeout.
  • Enabling output.s3 requires at least one of output.recording.publish or output.thumbnails.publish. Both flags default to false and require their own module plus the shared S3 destination to be enabled. S3 does not require recording when thumbnails publish.
  • Do not combine output.s3 with the legacy output.recording.s3 object. An enabled legacy alias implies recording publication only; it does not enable thumbnail publication.
  • Thumbnail publication requires local push or managed pull input. Cascade-only publication is a startup error; combining local and cascade inputs is valid.
  • Enabling HLS, LL-HLS, recording or thumbnail output requires output.root. WebRTC does not use output.root.
  • The viewer-facing output.http.listener is required for WebRTC and every storage-backed HTTP output. It remains required even for thumbnail-only operation.
  • WebRTC requires network.udpPort. Its UDP and optional TCP muxes must not overlap HTTP, operational, SRT ingest or cascade-output listeners on a shared bind address.
  • Anonymous WHEP is a closed-network mode. It cannot contain bearer credentials or TURN routes. Use bearer mode and explicit allowedOrigins when untrusted browser clients can reach the listener.
  • A TURN URL requires turnSharedSecret. internetReady also requires a turns: route, and credentialTTL must outlive bootstrap, setup, maximum session lifetime and allowed clock skew.