{ "swagger": "2.0", "info": { "description": "REST API for live stream configuration, ingest/publish lifecycle, DVR recordings, and event hooks.", "title": "Open Streamer API", "contact": {}, "version": "1.0" }, "basePath": "/", "paths": { "/config": { "get": { "description": "Returns available hardware accelerators (OS-detected), static enum lists, publisher listener ports, and the current runtime GlobalConfig.", "produces": [ "application/json" ], "tags": [ "system" ], "summary": "Get server configuration.", "responses": { "200": { "description": "OK", "schema": { "$ref": "#/definitions/apidocs.ConfigData" } } } }, "post": { "description": "Merges the request body into the current GlobalConfig and applies it — services are started or stopped to match.", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "system" ], "summary": "Partially update server configuration.", "parameters": [ { "description": "Partial configuration to merge", "name": "body", "in": "body", "required": true, "schema": { "$ref": "#/definitions/domain.GlobalConfig" } } ], "responses": { "200": { "description": "OK", "schema": { "$ref": "#/definitions/apidocs.ConfigUpdateResponse" } }, "400": { "description": "Bad Request", "schema": { "$ref": "#/definitions/apidocs.ErrorResponse" } }, "500": { "description": "Internal Server Error", "schema": { "$ref": "#/definitions/apidocs.ErrorResponse" } } } } }, "/config/defaults": { "get": { "description": "Static values the server fills in for unset configuration fields. Use as form placeholders so users see real defaults instead of \"default\" text.", "produces": [ "application/json" ], "tags": [ "system" ], "summary": "Get system configuration defaults.", "responses": { "200": { "description": "OK", "schema": { "$ref": "#/definitions/handler.configDefaultsResponse" } } } } }, "/config/transcoder/probe": { "post": { "description": "Inspects the binary at ffmpeg_path (empty = $PATH) and reports which required / optional encoders + muxers are available. Pure check — does not modify config.", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "system" ], "summary": "Probe an FFmpeg binary for app compatibility.", "parameters": [ { "description": "Path to probe (empty = use $PATH)", "name": "body", "in": "body", "required": true, "schema": { "$ref": "#/definitions/handler.probeRequest" } } ], "responses": { "200": { "description": "OK", "schema": { "$ref": "#/definitions/transcoder.ProbeResult" } }, "400": { "description": "Bad Request", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } }, "502": { "description": "Bad Gateway", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } } } } }, "/config/yaml": { "get": { "description": "Returns global_config, streams, and hooks bundled in a single YAML document. Pair with PUT /config/yaml to round-trip an editor.", "produces": [ "application/yaml" ], "tags": [ "system" ], "summary": "Get full system configuration as YAML", "responses": { "200": { "description": "YAML document", "schema": { "type": "string" } }, "500": { "description": "Internal Server Error", "schema": { "type": "object", "additionalProperties": true } } } }, "put": { "description": "Replaces global_config, streams, and hooks with the request body. Validation errors are returned as a list. On success, the runtime manager + coordinator diff against the previous state and start/stop/restart services accordingly.", "consumes": [ "application/yaml" ], "produces": [ "application/json" ], "tags": [ "system" ], "summary": "Replace full system configuration from YAML", "parameters": [ { "description": "Full system configuration YAML document", "name": "body", "in": "body", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "OK", "schema": { "type": "object", "additionalProperties": true } }, "400": { "description": "Bad Request", "schema": { "type": "object", "additionalProperties": true } }, "422": { "description": "Unprocessable Entity", "schema": { "type": "object", "additionalProperties": true } }, "500": { "description": "Internal Server Error", "schema": { "type": "object", "additionalProperties": true } } } } }, "/healthz": { "get": { "description": "Returns 200 if the process is up.", "tags": [ "system" ], "summary": "Liveness", "responses": { "200": { "description": "ok", "schema": { "type": "string" } } } } }, "/hooks": { "get": { "produces": [ "application/json" ], "tags": [ "hooks" ], "summary": "List hooks", "responses": { "200": { "description": "OK", "schema": { "$ref": "#/definitions/apidocs.HookList" } }, "500": { "description": "Internal Server Error", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } } } }, "post": { "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "hooks" ], "summary": "Create hook", "parameters": [ { "description": "Hook configuration", "name": "body", "in": "body", "required": true, "schema": { "$ref": "#/definitions/domain.Hook" } } ], "responses": { "201": { "description": "Created", "schema": { "$ref": "#/definitions/apidocs.HookData" } }, "400": { "description": "Bad Request", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } }, "500": { "description": "Internal Server Error", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } } } } }, "/hooks/{hid}": { "get": { "produces": [ "application/json" ], "tags": [ "hooks" ], "summary": "Get hook", "parameters": [ { "type": "string", "description": "Hook ID", "name": "hid", "in": "path", "required": true } ], "responses": { "200": { "description": "OK", "schema": { "$ref": "#/definitions/apidocs.HookData" } }, "404": { "description": "Not Found", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } } } }, "put": { "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "hooks" ], "summary": "Update hook", "parameters": [ { "type": "string", "description": "Hook ID", "name": "hid", "in": "path", "required": true }, { "description": "Hook configuration", "name": "body", "in": "body", "required": true, "schema": { "$ref": "#/definitions/domain.Hook" } } ], "responses": { "200": { "description": "OK", "schema": { "$ref": "#/definitions/apidocs.HookData" } }, "400": { "description": "Bad Request", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } }, "404": { "description": "Not Found", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } }, "500": { "description": "Internal Server Error", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } } } }, "delete": { "tags": [ "hooks" ], "summary": "Delete hook", "parameters": [ { "type": "string", "description": "Hook ID", "name": "hid", "in": "path", "required": true } ], "responses": { "204": { "description": "No Content" }, "500": { "description": "Internal Server Error", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } } } } }, "/hooks/{hid}/test": { "post": { "produces": [ "application/json" ], "tags": [ "hooks" ], "summary": "Test hook delivery", "parameters": [ { "type": "string", "description": "Hook ID", "name": "hid", "in": "path", "required": true } ], "responses": { "200": { "description": "OK", "schema": { "$ref": "#/definitions/apidocs.HookTestData" } }, "400": { "description": "Bad Request", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } }, "404": { "description": "Not Found", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } }, "502": { "description": "Bad Gateway", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } } } } }, "/readyz": { "get": { "description": "Returns 200 when the server accepts traffic (basic check).", "tags": [ "system" ], "summary": "Readiness", "responses": { "200": { "description": "ok", "schema": { "type": "string" } } } } }, "/recordings/{rid}": { "get": { "produces": [ "application/json" ], "tags": [ "recordings" ], "summary": "Get recording", "parameters": [ { "type": "string", "description": "Recording ID (= stream code)", "name": "rid", "in": "path", "required": true } ], "responses": { "200": { "description": "OK", "schema": { "$ref": "#/definitions/apidocs.RecordingData" } }, "404": { "description": "Not Found", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } } } } }, "/recordings/{rid}/info": { "get": { "produces": [ "application/json" ], "tags": [ "recordings" ], "summary": "DVR recording info", "parameters": [ { "type": "string", "description": "Recording ID (= stream code)", "name": "rid", "in": "path", "required": true } ], "responses": { "200": { "description": "OK", "schema": { "type": "object", "additionalProperties": true } }, "404": { "description": "Not Found", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } } } } }, "/recordings/{rid}/{file}": { "get": { "produces": [ "application/octet-stream" ], "tags": [ "recordings" ], "summary": "Serve recording file (segment or playlist; timeshift via query)", "parameters": [ { "type": "string", "description": "Recording ID (= stream code)", "name": "rid", "in": "path", "required": true }, { "type": "string", "description": "Segment filename (e.g. 000000.ts) or playlist.m3u8", "name": "file", "in": "path", "required": true }, { "type": "string", "description": "Timeshift: absolute start (RFC3339). Implies M3U8 dispatch", "name": "from", "in": "query" }, { "type": "integer", "description": "Timeshift: relative start (seconds from recording start)", "name": "offset_sec", "in": "query" }, { "type": "integer", "description": "Timeshift: window length in seconds (default: all remaining)", "name": "duration", "in": "query" } ], "responses": { "200": { "description": "OK", "schema": { "type": "file" } }, "404": { "description": "Not Found", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } } } } }, "/sessions": { "get": { "produces": [ "application/json" ], "tags": [ "sessions" ], "summary": "List play sessions", "parameters": [ { "type": "string", "description": "Filter by protocol (hls|dash|rtmp|srt|rtsp)", "name": "proto", "in": "query" }, { "type": "string", "description": "Filter by status (active|closed)", "name": "status", "in": "query" }, { "type": "integer", "description": "Max sessions to return (default 0 = no cap)", "name": "limit", "in": "query" } ], "responses": { "200": { "description": "OK", "schema": { "$ref": "#/definitions/apidocs.SessionList" } } } } }, "/sessions/{id}": { "get": { "produces": [ "application/json" ], "tags": [ "sessions" ], "summary": "Get a play session by ID", "parameters": [ { "type": "string", "description": "Session ID", "name": "id", "in": "path", "required": true } ], "responses": { "200": { "description": "OK", "schema": { "$ref": "#/definitions/domain.PlaySession" } }, "404": { "description": "Not Found", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } } } }, "delete": { "tags": [ "sessions" ], "summary": "Kick a play session", "parameters": [ { "type": "string", "description": "Session ID", "name": "id", "in": "path", "required": true } ], "responses": { "204": { "description": "kicked" }, "404": { "description": "Not Found", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } } } } }, "/streams": { "get": { "produces": [ "application/json" ], "tags": [ "streams" ], "summary": "List streams", "parameters": [ { "type": "string", "description": "Filter by status", "name": "status", "in": "query" } ], "responses": { "200": { "description": "OK", "schema": { "$ref": "#/definitions/apidocs.StreamList" } }, "400": { "description": "Bad Request", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } }, "500": { "description": "Internal Server Error", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } } } } }, "/streams/{code}": { "get": { "produces": [ "application/json" ], "tags": [ "streams" ], "summary": "Get stream", "parameters": [ { "type": "string", "description": "Stream code (a-zA-Z0-9_)", "name": "code", "in": "path", "required": true } ], "responses": { "200": { "description": "OK", "schema": { "$ref": "#/definitions/apidocs.StreamData" } }, "404": { "description": "Not Found", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } }, "500": { "description": "Internal Server Error", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } } } }, "post": { "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "streams" ], "summary": "Create or partial-update stream", "parameters": [ { "type": "string", "description": "Stream code", "name": "code", "in": "path", "required": true }, { "description": "Partial or full stream document", "name": "body", "in": "body", "required": true, "schema": { "$ref": "#/definitions/apidocs.StreamPutRequest" } } ], "responses": { "200": { "description": "OK", "schema": { "$ref": "#/definitions/apidocs.StreamData" } }, "201": { "description": "Created", "schema": { "$ref": "#/definitions/apidocs.StreamData" } }, "400": { "description": "Bad Request", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } }, "500": { "description": "Internal Server Error", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } } } }, "delete": { "tags": [ "streams" ], "summary": "Delete stream", "parameters": [ { "type": "string", "description": "Stream code", "name": "code", "in": "path", "required": true } ], "responses": { "204": { "description": "No Content" }, "500": { "description": "Internal Server Error", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } } } } }, "/streams/{code}/inputs/switch": { "post": { "produces": [ "application/json" ], "tags": [ "streams" ], "summary": "Manual input switch", "parameters": [ { "type": "string", "description": "Stream code", "name": "code", "in": "path", "required": true }, { "description": "Target input priority", "name": "body", "in": "body", "required": true, "schema": { "$ref": "#/definitions/apidocs.InputSwitchRequest" } } ], "responses": { "200": { "description": "OK", "schema": { "$ref": "#/definitions/apidocs.StreamActionData" } }, "400": { "description": "Bad Request", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } }, "404": { "description": "Not Found", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } }, "500": { "description": "Internal Server Error", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } } } } }, "/streams/{code}/recordings": { "get": { "produces": [ "application/json" ], "tags": [ "recordings" ], "summary": "List recordings by stream", "parameters": [ { "type": "string", "description": "Stream code", "name": "code", "in": "path", "required": true } ], "responses": { "200": { "description": "OK", "schema": { "$ref": "#/definitions/apidocs.RecordingList" } }, "500": { "description": "Internal Server Error", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } } } } }, "/streams/{code}/restart": { "post": { "produces": [ "application/json" ], "tags": [ "streams" ], "summary": "Restart stream pipeline", "parameters": [ { "type": "string", "description": "Stream code", "name": "code", "in": "path", "required": true } ], "responses": { "200": { "description": "OK", "schema": { "$ref": "#/definitions/apidocs.StreamActionData" } }, "400": { "description": "Bad Request", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } }, "404": { "description": "Not Found", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } }, "500": { "description": "Internal Server Error", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } } } } }, "/streams/{code}/sessions": { "get": { "produces": [ "application/json" ], "tags": [ "sessions" ], "summary": "List sessions for a stream", "parameters": [ { "type": "string", "description": "Stream code", "name": "code", "in": "path", "required": true }, { "type": "string", "description": "Filter by protocol", "name": "proto", "in": "query" }, { "type": "string", "description": "Filter by status", "name": "status", "in": "query" }, { "type": "integer", "description": "Max sessions to return", "name": "limit", "in": "query" } ], "responses": { "200": { "description": "OK", "schema": { "$ref": "#/definitions/apidocs.SessionList" } }, "400": { "description": "Bad Request", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } } } } }, "/vod": { "get": { "produces": [ "application/json" ], "tags": [ "vod" ], "summary": "List VOD mounts", "responses": { "200": { "description": "OK", "schema": { "$ref": "#/definitions/apidocs.VODMountList" } }, "500": { "description": "Internal Server Error", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } } } }, "post": { "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "vod" ], "summary": "Create VOD mount", "parameters": [ { "description": "VOD mount", "name": "body", "in": "body", "required": true, "schema": { "$ref": "#/definitions/domain.VODMount" } } ], "responses": { "201": { "description": "Created", "schema": { "$ref": "#/definitions/apidocs.VODMountData" } }, "400": { "description": "Bad Request", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } }, "409": { "description": "Conflict", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } }, "500": { "description": "Internal Server Error", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } } } } }, "/vod/{name}": { "get": { "produces": [ "application/json" ], "tags": [ "vod" ], "summary": "Get VOD mount", "parameters": [ { "type": "string", "description": "Mount name", "name": "name", "in": "path", "required": true } ], "responses": { "200": { "description": "OK", "schema": { "$ref": "#/definitions/apidocs.VODMountData" } }, "404": { "description": "Not Found", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } } } }, "put": { "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "vod" ], "summary": "Update VOD mount", "parameters": [ { "type": "string", "description": "Mount name", "name": "name", "in": "path", "required": true }, { "description": "VOD mount", "name": "body", "in": "body", "required": true, "schema": { "$ref": "#/definitions/domain.VODMount" } } ], "responses": { "200": { "description": "OK", "schema": { "$ref": "#/definitions/apidocs.VODMountData" } }, "400": { "description": "Bad Request", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } }, "404": { "description": "Not Found", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } }, "500": { "description": "Internal Server Error", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } } } }, "delete": { "tags": [ "vod" ], "summary": "Delete VOD mount", "parameters": [ { "type": "string", "description": "Mount name", "name": "name", "in": "path", "required": true } ], "responses": { "204": { "description": "No Content" }, "500": { "description": "Internal Server Error", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } } } } }, "/vod/{name}/files": { "get": { "produces": [ "application/json" ], "tags": [ "vod" ], "summary": "List files in a VOD mount", "parameters": [ { "type": "string", "description": "Mount name", "name": "name", "in": "path", "required": true }, { "type": "string", "description": "Subdirectory (relative, no leading slash)", "name": "path", "in": "query" } ], "responses": { "200": { "description": "OK", "schema": { "$ref": "#/definitions/apidocs.VODFileList" } }, "400": { "description": "Bad Request", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } }, "404": { "description": "Not Found", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } }, "500": { "description": "Internal Server Error", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } } } }, "post": { "consumes": [ "multipart/form-data" ], "produces": [ "application/json" ], "tags": [ "vod" ], "summary": "Upload a file to a VOD mount", "parameters": [ { "type": "string", "description": "Mount name", "name": "name", "in": "path", "required": true }, { "type": "string", "description": "Subdirectory (relative, no leading slash)", "name": "path", "in": "query" }, { "type": "file", "description": "Video file (mp4, mkv, mov, …)", "name": "file", "in": "formData", "required": true } ], "responses": { "201": { "description": "Created", "schema": { "$ref": "#/definitions/apidocs.VODFileData" } }, "400": { "description": "Bad Request", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } }, "404": { "description": "Not Found", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } }, "409": { "description": "Conflict", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } }, "413": { "description": "Request Entity Too Large", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } }, "500": { "description": "Internal Server Error", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } } } } }, "/vod/{name}/files/{path}": { "delete": { "tags": [ "vod" ], "summary": "Delete a file from a VOD mount", "parameters": [ { "type": "string", "description": "Mount name", "name": "name", "in": "path", "required": true }, { "type": "string", "description": "File path inside the mount (forward-slash separated)", "name": "path", "in": "path", "required": true } ], "responses": { "204": { "description": "No Content" }, "400": { "description": "Bad Request", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } }, "404": { "description": "Not Found", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } }, "500": { "description": "Internal Server Error", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } } } } }, "/vod/{name}/raw/{path}": { "get": { "description": "Returns the raw bytes of a file inside a VOD mount. Supports HTTP Range requests so an HTML5 video player can seek.", "produces": [ "application/octet-stream" ], "tags": [ "vod" ], "summary": "Stream a VOD file over HTTP", "parameters": [ { "type": "string", "description": "Mount name", "name": "name", "in": "path", "required": true }, { "type": "string", "description": "File path inside the mount (forward-slash separated)", "name": "path", "in": "path", "required": true } ], "responses": { "200": { "description": "OK", "schema": { "type": "file" } }, "206": { "description": "Partial Content", "schema": { "type": "file" } }, "400": { "description": "Bad Request", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } }, "404": { "description": "Not Found", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } } } } }, "/watermarks": { "get": { "produces": [ "application/json" ], "tags": [ "watermarks" ], "summary": "List watermark assets", "responses": { "200": { "description": "OK", "schema": { "$ref": "#/definitions/apidocs.WatermarkAssetList" } }, "500": { "description": "Internal Server Error", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } } } }, "post": { "consumes": [ "multipart/form-data" ], "produces": [ "application/json" ], "tags": [ "watermarks" ], "summary": "Upload a watermark image", "parameters": [ { "type": "file", "description": "Image file (PNG / JPG / GIF, ≤ 8 MiB)", "name": "file", "in": "formData", "required": true }, { "type": "string", "description": "Display name (defaults to filename)", "name": "name", "in": "query" } ], "responses": { "201": { "description": "Created", "schema": { "$ref": "#/definitions/apidocs.WatermarkAssetData" } }, "400": { "description": "Bad Request", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } }, "413": { "description": "Request Entity Too Large", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } }, "415": { "description": "Unsupported Media Type", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } }, "500": { "description": "Internal Server Error", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } } } } }, "/watermarks/{id}": { "get": { "produces": [ "application/json" ], "tags": [ "watermarks" ], "summary": "Get watermark metadata", "parameters": [ { "type": "string", "description": "Asset ID", "name": "id", "in": "path", "required": true } ], "responses": { "200": { "description": "OK", "schema": { "$ref": "#/definitions/apidocs.WatermarkAssetData" } }, "400": { "description": "Bad Request", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } }, "404": { "description": "Not Found", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } } } }, "delete": { "tags": [ "watermarks" ], "summary": "Delete a watermark asset", "parameters": [ { "type": "string", "description": "Asset ID", "name": "id", "in": "path", "required": true } ], "responses": { "204": { "description": "No Content" }, "400": { "description": "Bad Request", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } }, "404": { "description": "Not Found", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } }, "500": { "description": "Internal Server Error", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } } } } }, "/watermarks/{id}/raw": { "get": { "produces": [ "image/png" ], "tags": [ "watermarks" ], "summary": "Download watermark image", "parameters": [ { "type": "string", "description": "Asset ID", "name": "id", "in": "path", "required": true } ], "responses": { "200": { "description": "OK", "schema": { "type": "file" } }, "404": { "description": "Not Found", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } } } } } }, "definitions": { "apidocs.ConfigData": { "type": "object", "properties": { "audio_codecs": { "type": "array", "items": { "$ref": "#/definitions/domain.AudioCodec" } }, "global_config": { "$ref": "#/definitions/domain.GlobalConfig" }, "hw_accels": { "type": "array", "items": { "$ref": "#/definitions/domain.HWAccel" } }, "output_protocols": { "type": "array", "items": { "type": "string" } }, "ports": { "$ref": "#/definitions/apidocs.ConfigPorts" }, "stream_statuses": { "type": "array", "items": { "$ref": "#/definitions/domain.StreamStatus" } }, "version": { "$ref": "#/definitions/version.Info" }, "video_codecs": { "type": "array", "items": { "$ref": "#/definitions/domain.VideoCodec" } }, "watermark_positions": { "type": "array", "items": { "$ref": "#/definitions/domain.WatermarkPosition" } }, "watermark_types": { "type": "array", "items": { "$ref": "#/definitions/domain.WatermarkType" } } } }, "apidocs.ConfigPorts": { "type": "object", "properties": { "http_addr": { "type": "string" }, "rtmp_port": { "type": "integer" }, "rtsp_port": { "type": "integer" }, "srt_port": { "type": "integer" } } }, "apidocs.ConfigUpdateResponse": { "type": "object", "properties": { "global_config": { "$ref": "#/definitions/domain.GlobalConfig" }, "ports": { "$ref": "#/definitions/apidocs.ConfigPorts" } } }, "apidocs.ErrorBody": { "type": "object", "properties": { "error": { "type": "object", "properties": { "code": { "type": "string" }, "message": { "type": "string" } } } } }, "apidocs.ErrorResponse": { "type": "object", "properties": { "error": { "type": "object", "properties": { "code": { "type": "string" }, "message": { "type": "string" } } } } }, "apidocs.HookData": { "type": "object", "properties": { "data": { "$ref": "#/definitions/domain.Hook" } } }, "apidocs.HookList": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/definitions/domain.Hook" } }, "total": { "type": "integer" } } }, "apidocs.HookTestData": { "type": "object", "properties": { "data": { "$ref": "#/definitions/apidocs.HookTestInner" } } }, "apidocs.HookTestInner": { "type": "object", "properties": { "at": { "type": "string" }, "status": { "type": "string" } } }, "apidocs.InputSwitchRequest": { "type": "object", "properties": { "priority": { "type": "integer" } } }, "apidocs.RecordingData": { "type": "object", "properties": { "data": { "$ref": "#/definitions/domain.Recording" } } }, "apidocs.RecordingList": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/definitions/domain.Recording" } }, "total": { "type": "integer" } } }, "apidocs.SessionList": { "type": "object", "properties": { "sessions": { "type": "array", "items": { "$ref": "#/definitions/domain.PlaySession" } }, "stats": { "$ref": "#/definitions/apidocs.SessionStats" }, "total_count": { "type": "integer" } } }, "apidocs.SessionStats": { "type": "object", "properties": { "active": { "type": "integer" }, "closed_total": { "type": "integer" }, "idle_closed_total": { "type": "integer" }, "kicked_total": { "type": "integer" }, "opened_total": { "type": "integer" } } }, "apidocs.StreamActionData": { "type": "object", "properties": { "data": { "$ref": "#/definitions/apidocs.StreamActionInner" } } }, "apidocs.StreamActionInner": { "type": "object", "properties": { "status": { "type": "string" } } }, "apidocs.StreamData": { "type": "object", "properties": { "data": { "$ref": "#/definitions/apidocs.StreamResponse" } } }, "apidocs.StreamList": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/definitions/apidocs.StreamResponse" } }, "total": { "type": "integer" } } }, "apidocs.StreamPutRequest": { "type": "object", "properties": { "description": { "type": "string" }, "disabled": { "type": "boolean" }, "dvr": { "$ref": "#/definitions/domain.StreamDVRConfig" }, "inputs": { "type": "array", "items": { "$ref": "#/definitions/domain.Input" } }, "name": { "type": "string" }, "protocols": { "$ref": "#/definitions/domain.OutputProtocols" }, "push": { "type": "array", "items": { "$ref": "#/definitions/domain.PushDestination" } }, "stream_key": { "type": "string" }, "tags": { "type": "array", "items": { "type": "string" } }, "thumbnail": { "$ref": "#/definitions/domain.ThumbnailConfig" }, "transcoder": { "$ref": "#/definitions/domain.TranscoderConfig" }, "watermark": { "$ref": "#/definitions/domain.WatermarkConfig" } } }, "apidocs.StreamResponse": { "type": "object", "properties": { "code": { "description": "Code is the unique key chosen by the user ([a-zA-Z0-9_]).", "type": "string" }, "description": { "type": "string" }, "disabled": { "description": "Disabled when true excludes the stream from server bootstrap and rejects pipeline Start.", "type": "boolean" }, "dvr": { "description": "DVR overrides the global DVR settings for this specific stream.\nIf nil, the global config is used (when DVR is enabled globally).", "allOf": [ { "$ref": "#/definitions/domain.StreamDVRConfig" } ] }, "inputs": { "description": "Inputs are the available ingest sources ordered by Priority.\nThe Stream Manager monitors health and switches between them on failure.", "type": "array", "items": { "$ref": "#/definitions/domain.Input" } }, "name": { "type": "string" }, "protocols": { "description": "Protocols defines which delivery protocols are opened for this stream.\nThe server opens a listener/packager for each enabled protocol.\nProtocol-level config (ports, segment duration, CDN URL) lives in server config.", "allOf": [ { "$ref": "#/definitions/domain.OutputProtocols" } ] }, "push": { "description": "Push is the list of external destinations the server actively pushes to.\nEach entry defines one push target (YouTube, Facebook, Twitch, CDN relay, etc.).", "type": "array", "items": { "$ref": "#/definitions/domain.PushDestination" } }, "runtime": { "$ref": "#/definitions/manager.RuntimeStatus" }, "stream_key": { "description": "StreamKey is used to authenticate RTMP/SRT push ingest.", "type": "string" }, "tags": { "type": "array", "items": { "type": "string" } }, "thumbnail": { "description": "Thumbnail controls periodic screenshot generation for preview images.", "allOf": [ { "$ref": "#/definitions/domain.ThumbnailConfig" } ] }, "transcoder": { "description": "Transcoder controls encoding/decoding settings.\nnil means no transcoding for this stream.", "allOf": [ { "$ref": "#/definitions/domain.TranscoderConfig" } ] }, "watermark": { "description": "Watermark is an optional text or image overlay applied before encoding.", "allOf": [ { "$ref": "#/definitions/domain.WatermarkConfig" } ] } } }, "apidocs.VODFileData": { "type": "object", "properties": { "data": { "$ref": "#/definitions/vod.FileEntry" } } }, "apidocs.VODFileList": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/definitions/vod.FileEntry" } }, "path": { "type": "string" }, "total": { "type": "integer" } } }, "apidocs.VODMountData": { "type": "object", "properties": { "data": { "$ref": "#/definitions/domain.VODMount" } } }, "apidocs.VODMountList": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/definitions/domain.VODMount" } }, "total": { "type": "integer" } } }, "apidocs.WatermarkAssetData": { "type": "object", "properties": { "data": { "$ref": "#/definitions/domain.WatermarkAsset" } } }, "apidocs.WatermarkAssetList": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/definitions/domain.WatermarkAsset" } }, "dir": { "type": "string" }, "total": { "type": "integer" } } }, "config.BufferConfig": { "type": "object", "properties": { "capacity": { "description": "Capacity is the number of MPEG-TS packets per stream buffer.", "type": "integer" } } }, "config.CORSConfig": { "type": "object", "properties": { "allow_credentials": { "description": "AllowCredentials sets Access-Control-Allow-Credentials. Must be false\nwhen AllowedOrigins contains \"*\".", "type": "boolean" }, "allowed_headers": { "description": "AllowedHeaders lists Access-Control-Allow-Headers; empty uses a common API default set.", "type": "array", "items": { "type": "string" } }, "allowed_methods": { "description": "AllowedMethods lists Access-Control-Allow-Methods; empty uses a REST default set.", "type": "array", "items": { "type": "string" } }, "allowed_origins": { "description": "AllowedOrigins lists values for Access-Control-Allow-Origin. Use \"*\"\nfor any origin (cannot be used together with AllowCredentials).", "type": "array", "items": { "type": "string" } }, "enabled": { "description": "Enabled turns CORS middleware on for the HTTP listener.", "type": "boolean" }, "exposed_headers": { "description": "ExposedHeaders lists Access-Control-Expose-Headers.", "type": "array", "items": { "type": "string" } }, "max_age": { "description": "MaxAge is the preflight cache duration in seconds (Access-Control-Max-Age).", "type": "integer" } } }, "config.HooksConfig": { "type": "object", "properties": { "batch_flush_interval_sec": { "description": "BatchFlushIntervalSec is the global default for the per-hook flush\ntimer in seconds. Per-hook overrides win; code default is\nDefaultHookBatchFlushIntervalSec.", "type": "integer" }, "batch_max_items": { "description": "BatchMaxItems is the global default for HTTP hook batch size.\nPer-hook BatchMaxItems overrides this; the code default\n(DefaultHookBatchMaxItems) wins only when both are 0.", "type": "integer" }, "batch_max_queue_items": { "description": "BatchMaxQueueItems is the global default for the per-hook in-memory\nqueue cap. Per-hook overrides win; code default is\nDefaultHookBatchMaxQueueItems.", "type": "integer" }, "worker_count": { "description": "WorkerCount sizes the events.Bus worker pool that fans events out\nto subscribers. Each worker invokes the registered handler for an\nincoming event; with the batched HTTP delivery, the hook handler\njust enqueues into a per-hook batcher (~µs) so this number rarely\nneeds tuning. 1-4 covers nearly every workload. 0 = use 4.", "type": "integer" } } }, "config.IngestorConfig": { "type": "object", "properties": { "hls_max_segment_buffer": { "description": "HLSMaxSegmentBuffer caps the number of pre-fetched HLS segments held in memory.\nThis is a server-wide memory guard, not a per-stream policy.", "type": "integer" } } }, "config.ListenersConfig": { "type": "object", "properties": { "rtmp": { "$ref": "#/definitions/config.RTMPListenerConfig" }, "rtsp": { "$ref": "#/definitions/config.RTSPListenerConfig" }, "srt": { "$ref": "#/definitions/config.SRTListenerConfig" } } }, "config.LogConfig": { "type": "object", "properties": { "format": { "description": "text | json", "type": "string" }, "level": { "description": "trace | debug | info | warn | error", "type": "string" } } }, "config.ManagerConfig": { "type": "object", "properties": { "input_packet_timeout_sec": { "description": "InputPacketTimeoutSec is the maximum gap without a successful read on the\nactive input before it is marked failed. Pull protocols that deliver in\nbursts (e.g. HLS: one segment per Read) need this at least as large as the\ntypical interval between reads (segment duration + playlist poll), or a\nhealthy primary will be falsely failed over to a lower priority.", "type": "integer" } } }, "config.PublisherConfig": { "type": "object", "properties": { "dash": { "$ref": "#/definitions/config.PublisherDASHConfig" }, "hls": { "$ref": "#/definitions/config.PublisherHLSConfig" } } }, "config.PublisherDASHConfig": { "type": "object", "properties": { "dir": { "type": "string" }, "live_ephemeral": { "description": "Live* mirror HLS packaging semantics for the DASH muxer.", "type": "boolean" }, "live_history": { "type": "integer" }, "live_segment_sec": { "type": "integer" }, "live_window": { "type": "integer" } } }, "config.PublisherHLSConfig": { "type": "object", "properties": { "dir": { "type": "string" }, "live_ephemeral": { "description": "LiveEphemeral enables bounded retention (sliding manifest, delete old segments).", "type": "boolean" }, "live_history": { "description": "LiveHistory is extra segments kept on disk after they leave the manifest.", "type": "integer" }, "live_segment_sec": { "description": "LiveSegmentSec is segment duration in seconds.", "type": "integer" }, "live_window": { "description": "LiveWindow is the sliding window size (segments) in the playlist.", "type": "integer" } } }, "config.RTMPListenerConfig": { "type": "object", "properties": { "enabled": { "type": "boolean" }, "listen_host": { "type": "string" }, "port": { "description": "default 1935", "type": "integer" } } }, "config.RTSPListenerConfig": { "type": "object", "properties": { "enabled": { "type": "boolean" }, "listen_host": { "type": "string" }, "port": { "description": "default 554", "type": "integer" }, "transport": { "description": "Transport is \"tcp\" (default) or \"udp\" for the RTSP muxer.", "type": "string" } } }, "config.SRTListenerConfig": { "type": "object", "properties": { "enabled": { "type": "boolean" }, "latency_ms": { "description": "LatencyMS is the SRT latency in milliseconds applied to the listener.", "type": "integer" }, "listen_host": { "type": "string" }, "port": { "description": "default 9999", "type": "integer" } } }, "config.ServerConfig": { "type": "object", "properties": { "cors": { "$ref": "#/definitions/config.CORSConfig" }, "http_addr": { "type": "string" }, "pprof_addr": { "description": "PprofAddr enables Go's net/http/pprof + runtime introspection\nendpoints on a SEPARATE listener. Default empty = disabled.\nRecommend \"127.0.0.1:6060\" so the listener stays loopback-only —\npprof exposes goroutine stacks and heap layouts that should never\nbe reachable from the public network. Reach it from a remote box\nvia SSH tunnel: `ssh -L 6060:127.0.0.1:6060 host`.\n\nEndpoints exposed when set:\n /debug/pprof/heap — heap snapshot (live objects)\n /debug/pprof/allocs — cumulative alloc profile\n /debug/pprof/goroutine — goroutine stack dump (?debug=2 for text)\n /debug/pprof/profile — 30s CPU profile\n /debug/pprof/block — blocking profile (needs SetBlockProfileRate)\n /debug/pprof/mutex — mutex contention (needs SetMutexProfileFraction)", "type": "string" } } }, "config.SessionsConfig": { "type": "object", "properties": { "enabled": { "description": "Enabled toggles the entire feature. When false, the tracker no-ops and\nthe API endpoints return empty.", "type": "boolean" }, "geoip_db_path": { "description": "GeoIPDBPath, when non-empty, points to a MaxMind .mmdb file\n(GeoLite2-Country / GeoLite2-City; commercial GeoIP2 also works).\nThe wiring layer opens it once at startup and uses it to fill\nPlaySession.Country with the ISO 3166-1 alpha-2 code. When empty\n(default) or when opening fails, sessions degrade silently to\nCountry=\"\" via NullGeoIP — no impact on session tracking itself.\n\nHot-reloading the file on disk requires a server restart; the\n.mmdb is mmap'd for the lifetime of the process.", "type": "string" }, "idle_timeout_sec": { "description": "IdleTimeoutSec is how long a session may go without activity before the\nreaper closes it. Default 30 s when \u003c= 0.", "type": "integer" }, "max_lifetime_sec": { "description": "MaxLifetimeSec, when \u003e 0, hard-closes any session older than this even\nif it's still seeing activity. 0 disables the cap.", "type": "integer" } } }, "config.TranscoderConfig": { "type": "object", "properties": { "ffmpeg_path": { "type": "string" } } }, "config.WatermarksConfig": { "type": "object", "properties": { "dir": { "description": "Dir is the assets directory. Must be writable by the open-streamer\nprocess. Defaults to \"./watermarks\" when empty (matches the layout\ninstall scripts use under /var/lib/open-streamer/watermarks).", "type": "string" } } }, "domain.AudioCodec": { "type": "string", "enum": [ "aac", "mp2a", "mp3", "ac3", "eac3", "copy" ], "x-enum-comments": { "AudioCodecAAC": "default for HLS/DASH", "AudioCodecAC3": "Dolby Digital — broadcast use", "AudioCodecCopy": "passthrough — no re-encode", "AudioCodecEAC3": "Dolby Digital Plus — premium HD/UHD broadcast (5.1+)", "AudioCodecMP2": "MPEG-1/2 Audio Layer II — DVB broadcast contribution feeds", "AudioCodecMP3": "legacy compatibility" }, "x-enum-descriptions": [ "default for HLS/DASH", "MPEG-1/2 Audio Layer II — DVB broadcast contribution feeds", "legacy compatibility", "Dolby Digital — broadcast use", "Dolby Digital Plus — premium HD/UHD broadcast (5.1+)", "passthrough — no re-encode" ], "x-enum-varnames": [ "AudioCodecAAC", "AudioCodecMP2", "AudioCodecMP3", "AudioCodecAC3", "AudioCodecEAC3", "AudioCodecCopy" ] }, "domain.AudioTranscodeConfig": { "type": "object", "properties": { "bitrate": { "description": "Bitrate is the audio bitrate in kbps.", "type": "integer" }, "channels": { "description": "Channels: 1 = mono, 2 = stereo, 6 = 5.1.", "type": "integer" }, "codec": { "$ref": "#/definitions/domain.AudioCodec" }, "copy": { "description": "Copy copies origin audio without re-encoding.", "type": "boolean" }, "language": { "description": "Language is ISO 639-1 code, e.g. \"en\", \"vi\".", "type": "string" }, "normalize": { "description": "Normalize applies EBU R128 loudness normalization.", "type": "boolean" }, "sample_rate": { "description": "SampleRate is output sample rate in Hz.", "type": "integer" } } }, "domain.DecoderConfig": { "type": "object", "properties": { "name": { "description": "Name is the FFmpeg decoder name.\n\"\" = let FFmpeg choose automatically.\nExamples: \"h264_cuvid\", \"h264_qsv\".", "type": "string" } } }, "domain.ErrorEntry": { "type": "object", "properties": { "at": { "type": "string" }, "message": { "type": "string" } } }, "domain.EventType": { "type": "string", "enum": [ "stream.created", "stream.started", "stream.stopped", "stream.deleted", "input.connected", "input.reconnecting", "input.degraded", "input.failed", "input.failover", "recording.started", "recording.stopped", "recording.failed", "segment.written", "transcoder.started", "transcoder.stopped", "transcoder.error", "session.opened", "session.closed" ], "x-enum-comments": { "EventInputConnected": "source connected successfully", "EventInputDegraded": "error detected by manager", "EventInputFailed": "worker exited / non-retriable", "EventInputFailover": "switched to lower-priority input", "EventInputReconnecting": "transient error, retrying" }, "x-enum-descriptions": [ "", "", "", "", "source connected successfully", "transient error, retrying", "error detected by manager", "worker exited / non-retriable", "switched to lower-priority input", "", "", "", "", "", "", "", "", "" ], "x-enum-varnames": [ "EventStreamCreated", "EventStreamStarted", "EventStreamStopped", "EventStreamDeleted", "EventInputConnected", "EventInputReconnecting", "EventInputDegraded", "EventInputFailed", "EventInputFailover", "EventRecordingStarted", "EventRecordingStopped", "EventRecordingFailed", "EventSegmentWritten", "EventTranscoderStarted", "EventTranscoderStopped", "EventTranscoderError", "EventSessionOpened", "EventSessionClosed" ] }, "domain.GlobalConfig": { "type": "object", "properties": { "buffer": { "$ref": "#/definitions/config.BufferConfig" }, "hooks": { "$ref": "#/definitions/config.HooksConfig" }, "ingestor": { "$ref": "#/definitions/config.IngestorConfig" }, "listeners": { "$ref": "#/definitions/config.ListenersConfig" }, "log": { "$ref": "#/definitions/config.LogConfig" }, "manager": { "$ref": "#/definitions/config.ManagerConfig" }, "publisher": { "$ref": "#/definitions/config.PublisherConfig" }, "server": { "$ref": "#/definitions/config.ServerConfig" }, "sessions": { "$ref": "#/definitions/config.SessionsConfig" }, "transcoder": { "$ref": "#/definitions/config.TranscoderConfig" }, "watermarks": { "$ref": "#/definitions/config.WatermarksConfig" } } }, "domain.HWAccel": { "type": "string", "enum": [ "none", "nvenc", "vaapi", "videotoolbox", "qsv" ], "x-enum-comments": { "HWAccelNVENC": "NVIDIA GPU (h264_nvenc, hevc_nvenc)", "HWAccelNone": "CPU only (libx264, libx265)", "HWAccelQSV": "Intel Quick Sync Video", "HWAccelVAAPI": "Intel/AMD GPU via VA-API (Linux)", "HWAccelVideoToolbox": "Apple GPU (macOS)" }, "x-enum-descriptions": [ "CPU only (libx264, libx265)", "NVIDIA GPU (h264_nvenc, hevc_nvenc)", "Intel/AMD GPU via VA-API (Linux)", "Apple GPU (macOS)", "Intel Quick Sync Video" ], "x-enum-varnames": [ "HWAccelNone", "HWAccelNVENC", "HWAccelVAAPI", "HWAccelVideoToolbox", "HWAccelQSV" ] }, "domain.Hook": { "type": "object", "properties": { "batch_flush_interval_sec": { "description": "BatchFlushIntervalSec is the maximum time a batch may sit before being\nflushed even when below BatchMaxItems. 0 = use HooksConfig default,\nthen DefaultHookBatchFlushIntervalSec.", "type": "integer" }, "batch_max_items": { "description": "BatchMaxItems caps the number of events bundled into one HTTP POST\nbody. 0 = use HooksConfig.BatchMaxItems, then DefaultHookBatchMaxItems.\nIgnored for File hooks (they always write one event per line).", "type": "integer" }, "batch_max_queue_items": { "description": "BatchMaxQueueItems caps the per-hook in-memory queue (pending +\nre-queued failures). When exceeded, the OLDEST events are dropped\nwith a warning log so the queue never grows unbounded against an\nunreachable target. 0 = use HooksConfig default, then\nDefaultHookBatchMaxQueueItems.", "type": "integer" }, "enabled": { "type": "boolean" }, "event_types": { "description": "EventTypes filters which events trigger delivery. Empty = all events.", "type": "array", "items": { "$ref": "#/definitions/domain.EventType" } }, "id": { "type": "string" }, "max_retries": { "description": "MaxRetries is the number of delivery attempts before giving up.\n0 means use the server default (3). For HTTP hooks this caps retries\ninside a single batch flush; events that still fail are re-queued\nfor the next flush regardless of MaxRetries.", "type": "integer" }, "metadata": { "description": "Metadata holds user-defined key-value pairs merged into every event payload\ndelivered by this hook. Useful for tagging events with custom context\n(e.g. environment, tenant ID, region) without modifying the server config.", "type": "object", "additionalProperties": { "type": "string" } }, "name": { "type": "string" }, "secret": { "description": "HMAC-SHA256 signing secret (HTTP only)", "type": "string" }, "stream_codes": { "description": "StreamCodes filters delivery by stream code.\nOnly and Except are mutually exclusive; Only takes precedence when both are set.\nOmitting the field (nil) means all streams are included.", "allOf": [ { "$ref": "#/definitions/domain.StreamCodeFilter" } ] }, "target": { "description": "HTTP(S) URL or absolute file path", "type": "string" }, "timeout_sec": { "description": "TimeoutSec is the per-attempt delivery timeout in seconds.\n0 means use the server default (10s).", "type": "integer" }, "type": { "$ref": "#/definitions/domain.HookType" } } }, "domain.HookType": { "type": "string", "enum": [ "http", "file" ], "x-enum-varnames": [ "HookTypeHTTP", "HookTypeFile" ] }, "domain.Input": { "type": "object", "properties": { "headers": { "description": "Headers are arbitrary HTTP headers sent with every request for HTTP/HLS inputs.\nCommon uses:\n \"Authorization\": \"Bearer \u003ctoken\u003e\"\n \"Authorization\": \"Basic \u003cbase64(user:pass)\u003e\"\n \"X-Custom-Token\": \"secret\"", "type": "object", "additionalProperties": { "type": "string" } }, "net": { "description": "Net controls reconnect and timeout behaviour.", "allOf": [ { "$ref": "#/definitions/domain.InputNetConfig" } ] }, "params": { "description": "Params are extra URL query parameters merged into the source URL before\nconnecting. Used for protocols that carry credentials or options in the\nquery string (SRT ?passphrase=, S3 ?access_key= / ?secret_key=, etc.).\n \"passphrase\": \"my-srt-passphrase\"\n \"access_key\": \"AKID...\" (S3)\n \"secret_key\": \"wJal...\" (S3)", "type": "object", "additionalProperties": { "type": "string" } }, "pids": { "description": "Pids is an explicit allowlist of TS PIDs to keep — every other PID\nis dropped at ingest. Used when the source PSI is unreliable (legacy\nencoders with malformed PAT/PMT) or when the operator wants to cherry-\npick a subset (e.g. drop a teletext PID, keep only one of N audio\nlanguages). The filter is purely PID-level: no PAT/PMT rewrite, no\nCRC recompute. Operators must include every PID needed for playback\n(typically PID 0 for PAT, the PMT PID, and the desired ES PIDs).\n\nLayers with Program when both are set: Program runs first (auto-\ndetect ES PIDs + rewrite PAT to single-program), then Pids further\nrestricts the output. Empty (default) disables the filter.\n\nCurrently applies to UDP only — same rationale as Program.", "type": "array", "items": { "type": "integer" } }, "priority": { "description": "Priority determines failover order. Lower value = higher priority.\nThe Stream Manager always prefers the lowest-priority alive input.", "type": "integer" }, "program": { "description": "Program selects a single MPEG-TS program when the source is a\nmulti-program transport stream (MPTS) — common in DVB headend feeds\nwhere one multicast carries many channels. When \u003e 0, the ingest\npipeline rewrites the PAT to advertise only the chosen program and\ndrops PMT / ES packets belonging to other programs, producing a\nclean SPTS for downstream HLS / DASH / push consumers.\n\nZero (default) disables filtering — the entire stream is forwarded\nunchanged. Currently applies to UDP only; HLS / SRT / File ingest\nare SPTS by convention so the filter is not wired for those (extend\nreader.go if a real MPTS file/SRT use case arises). Ignored for\nRTSP / RTMP, which are single-program by protocol design.", "type": "integer" }, "url": { "description": "URL is the source endpoint. See the package doc for supported formats.", "type": "string" } } }, "domain.InputNetConfig": { "type": "object", "properties": { "insecure_tls": { "description": "InsecureTLS disables TLS certificate verification for HTTPS pulls\n(HLS playlist + segment GETs). Default false — leave secure-by-default\nfor production. Use only when the source uses a self-signed,\nexpired, or otherwise-invalid certificate that you trust at the\nnetwork level (private VLAN, fixed IP allowlist).", "type": "boolean" }, "timeout_sec": { "description": "TimeoutSec is the per-protocol operation budget the reader applies\nwhen this input is opened. Semantics differ by protocol:\n\n - HLS: HTTP request timeout (entire round-trip incl. body) for\n the playlist GET. Segment GETs derive from this — typically\n 4× the playlist budget, floored at the segment default.\n - RTMP: TCP dial timeout (handshake budget).\n - RTSP: dial + initial read timeout (until first packet).\n - SRT: connection / handshake timeout.\n\nZero uses the reader's per-protocol default\n(DefaultHLSPlaylistTimeoutSec for HLS; DefaultRTMPTimeoutSec /\nDefaultRTSPTimeoutSec for the rest).", "type": "integer" } } }, "domain.InterlaceMode": { "type": "string", "enum": [ "auto", "tff", "bff", "progressive" ], "x-enum-comments": { "InterlaceAuto": "detect parity each frame", "InterlaceBottomField": "bottom field first (legacy DV)", "InterlaceProgressive": "assert source is progressive — skip filter", "InterlaceTopField": "top field first (BBC HD, most ATSC)" }, "x-enum-descriptions": [ "detect parity each frame", "top field first (BBC HD, most ATSC)", "bottom field first (legacy DV)", "assert source is progressive — skip filter" ], "x-enum-varnames": [ "InterlaceAuto", "InterlaceTopField", "InterlaceBottomField", "InterlaceProgressive" ] }, "domain.MediaTrackInfo": { "type": "object", "properties": { "bitrate_kbps": { "type": "integer" }, "codec": { "description": "\"h264\" | \"h265\" | \"aac\" | \"mp2a\"", "type": "string" }, "height": { "description": "video only", "type": "integer" }, "kind": { "$ref": "#/definitions/domain.MediaTrackKind" }, "width": { "description": "video only", "type": "integer" } } }, "domain.MediaTrackKind": { "type": "string", "enum": [ "video", "audio" ], "x-enum-varnames": [ "MediaTrackVideo", "MediaTrackAudio" ] }, "domain.OutputProtocols": { "type": "object", "properties": { "dash": { "description": "DASH enables MPEG-DASH packaging over HTTP.\nRequired for Widevine/PlayReady DRM.", "type": "boolean" }, "hls": { "description": "HLS enables Apple HTTP Live Streaming (m3u8 + segments over HTTP).\nCompatible with browsers, iOS, Android, Smart TVs.", "type": "boolean" }, "mpegts": { "description": "MPEGTS exposes raw MPEG-TS over chunked HTTP at /\u003ccode\u003e/mpegts —\nthe lowest-latency relay path between Open-Streamer instances (and\nany HTTP client that can consume chunked TS, e.g. ffmpeg / VLC).\nLatency is bounded only by network RTT and one buffer-hub chunk\n(typically 50–200 ms vs 4–10 s for HLS / DASH).\n\nNo goroutine is started per-stream; the endpoint subscribes to the\nplayback buffer on demand. Disabling the flag turns the endpoint\ninto a 404 for that stream so operators can opt out per-stream\nwithout changing the global router.", "type": "boolean" }, "rtmp": { "description": "RTMP opens an RTMP publish endpoint for legacy players/CDNs.", "type": "boolean" }, "rtsp": { "description": "RTSP opens an RTSP listener for pull clients (VLC, broadcast tools).", "type": "boolean" }, "srt": { "description": "SRT opens an SRT listener port for contribution-quality pull.", "type": "boolean" } } }, "domain.PlaySession": { "type": "object", "properties": { "bytes": { "description": "Bytes is the cumulative bytes sent to this client since session open.\nHLS/DASH: sum of segment + manifest response bodies. RTMP/SRT: write\nbytes from the publisher's outbound pipeline.", "type": "integer" }, "close_reason": { "description": "CloseReason explains why the session ended. Empty while active.", "allOf": [ { "$ref": "#/definitions/domain.SessionCloseReason" } ] }, "closed_at": { "description": "ClosedAt is set when the session ends. nil while active.", "type": "string" }, "country": { "description": "Country is an ISO 3166-1 alpha-2 country code from the configured\nGeoIP resolver, or \"\" when GeoIP is disabled / lookup failed.", "type": "string" }, "dvr": { "description": "DVR is true when playback originated from the DVR / recording route\n(/recordings/{rid}/...), false for live-edge mediaserve hits. Set\nby sessions.DVRHTTPMiddleware on the recording sub-router. The flag\nalso participates in the session fingerprint, so a viewer watching\nboth live and timeshift gets two distinct session records.", "type": "boolean" }, "id": { "description": "ID uniquely identifies the session across the server's lifetime. For\nsegment protocols (HLS/DASH) it is the deterministic fingerprint hash\n(so reconnects within the idle window collapse onto one session); for\nconnection-bound protocols (RTMP/SRT/RTSP) it is a random UUID.", "type": "string" }, "ip": { "description": "IP is the remote peer address (no port). For HTTP-based protocols this\nis the X-Forwarded-For head when present, otherwise the RemoteAddr.", "type": "string" }, "named_by": { "description": "NamedBy records how UserName was resolved.", "allOf": [ { "$ref": "#/definitions/domain.SessionNamedBy" } ] }, "opened_at": { "description": "OpenedAt is the time the first activity for this session was observed.", "type": "string" }, "proto": { "description": "Protocol is the wire protocol used to deliver this session.", "allOf": [ { "$ref": "#/definitions/domain.SessionProto" } ] }, "query_string": { "description": "QueryString is the raw query of the FIRST request that opened the\nsession (HLS/DASH) — useful when token/abr-variant info is encoded there.", "type": "string" }, "referer": { "description": "Referer is the HTTP Referer when present (HLS/DASH only).", "type": "string" }, "secure": { "description": "Secure is true when the underlying transport was TLS / SRTS / RTMPS.", "type": "boolean" }, "started_at": { "description": "StartedAt is when the first byte of media was delivered (segment data\nfor HLS/DASH, first frame after RTMP play handshake). Zero before\nmedia flows.", "type": "string" }, "stream_code": { "description": "StreamCode is the foreign key to Stream.Code.", "type": "string" }, "token": { "description": "Token is the value of `?token=` from the first request, when present.\nWhen set, NamedBy is SessionNamedByToken.", "type": "string" }, "updated_at": { "description": "UpdatedAt is the time of the most recent activity. Idle reaper closes\nsessions whose UpdatedAt is older than (now - idle_timeout).", "type": "string" }, "user_agent": { "description": "UserAgent is the browser/player User-Agent (HTTP) or the equivalent\nflashVer field for RTMP. May be empty.", "type": "string" }, "user_name": { "description": "UserName is a human label. Resolved from token claims if available,\notherwise the fingerprint hash short form. Empty when no identity could\nbe resolved.", "type": "string" } } }, "domain.PushDestination": { "type": "object", "properties": { "comment": { "description": "Comment is a human-readable note for this destination.", "type": "string" }, "enabled": { "description": "Enabled controls whether this destination is active.", "type": "boolean" }, "limit": { "description": "Limit is the maximum number of retry attempts. 0 = unlimited.", "type": "integer" }, "retry_timeout_sec": { "description": "RetryTimeoutSec is the delay between retry attempts in seconds.", "type": "integer" }, "status": { "description": "Status is a runtime-only field updated by the publisher.\nNot persisted to storage.", "allOf": [ { "$ref": "#/definitions/domain.PushStatus" } ] }, "timeout_sec": { "description": "TimeoutSec is the connection/write timeout in seconds.", "type": "integer" }, "url": { "description": "URL is the destination ingest endpoint.\nSupported schemes:\n rtmp:// — plain TCP, default port 1935 (e.g. rtmp://a.rtmp.youtube.com/live2/{key})\n rtmps:// — TLS-wrapped RTMP, default port 443 (e.g. rtmps://live-api-s.facebook.com:443/rtmp/{key})", "type": "string" } } }, "domain.PushStatus": { "type": "string", "enum": [ "idle", "connecting", "active", "retrying", "failed", "disabled" ], "x-enum-varnames": [ "PushStatusIdle", "PushStatusConnecting", "PushStatusActive", "PushStatusRetrying", "PushStatusFailed", "PushStatusDisabled" ] }, "domain.Recording": { "type": "object", "properties": { "id": { "type": "string" }, "segment_dir": { "description": "SegmentDir is the absolute path to the directory holding TS files,\nplaylist.m3u8, and index.json.", "type": "string" }, "started_at": { "type": "string" }, "status": { "$ref": "#/definitions/domain.RecordingStatus" }, "stopped_at": { "type": "string" }, "stream_code": { "type": "string" } } }, "domain.RecordingStatus": { "type": "string", "enum": [ "recording", "stopped", "failed" ], "x-enum-varnames": [ "RecordingStatusRecording", "RecordingStatusStopped", "RecordingStatusFailed" ] }, "domain.ResizeMode": { "type": "string", "enum": [ "pad", "crop", "stretch", "fit" ], "x-enum-comments": { "ResizeModeCrop": "fill: keep aspect, crop excess", "ResizeModeFit": "keep aspect, no padding (output may be smaller than W:H)", "ResizeModePad": "letterbox: keep aspect, fill remainder with black", "ResizeModeStretch": "distort: scale to W:H, ignore source aspect" }, "x-enum-descriptions": [ "letterbox: keep aspect, fill remainder with black", "fill: keep aspect, crop excess", "distort: scale to W:H, ignore source aspect", "keep aspect, no padding (output may be smaller than W:H)" ], "x-enum-varnames": [ "ResizeModePad", "ResizeModeCrop", "ResizeModeStretch", "ResizeModeFit" ] }, "domain.SessionCloseReason": { "type": "string", "enum": [ "idle", "client_gone", "shutdown", "kicked" ], "x-enum-comments": { "SessionCloseClient": "TCP/UDP peer closed (RTMP/SRT/RTSP)", "SessionCloseIdle": "no activity within the configured idle window", "SessionCloseKicked": "operator force-closed via API", "SessionCloseShutdown": "server shutting down" }, "x-enum-descriptions": [ "no activity within the configured idle window", "TCP/UDP peer closed (RTMP/SRT/RTSP)", "server shutting down", "operator force-closed via API" ], "x-enum-varnames": [ "SessionCloseIdle", "SessionCloseClient", "SessionCloseShutdown", "SessionCloseKicked" ] }, "domain.SessionNamedBy": { "type": "string", "enum": [ "token", "config", "fingerprint" ], "x-enum-comments": { "SessionNamedByConfig": "resolved from server-side config (future: signed URL)", "SessionNamedByFingerprint": "synthesised hash of ip+ua+stream", "SessionNamedByToken": "resolved from a `token` query param" }, "x-enum-descriptions": [ "resolved from a `token` query param", "resolved from server-side config (future: signed URL)", "synthesised hash of ip+ua+stream" ], "x-enum-varnames": [ "SessionNamedByToken", "SessionNamedByConfig", "SessionNamedByFingerprint" ] }, "domain.SessionProto": { "type": "string", "enum": [ "hls", "dash", "rtmp", "srt", "rtsp", "mpegts" ], "x-enum-varnames": [ "SessionProtoHLS", "SessionProtoDASH", "SessionProtoRTMP", "SessionProtoSRT", "SessionProtoRTSP", "SessionProtoMPEGTS" ] }, "domain.StreamCodeFilter": { "type": "object", "properties": { "except": { "description": "Except delivers events for all streams except those in this list.", "type": "array", "items": { "type": "string" } }, "only": { "description": "Only delivers events only for streams in this list.", "type": "array", "items": { "type": "string" } } } }, "domain.StreamDVRConfig": { "type": "object", "properties": { "enabled": { "type": "boolean" }, "max_size_gb": { "description": "MaxSizeGB caps total disk usage. Oldest segments pruned when exceeded.\n0 = no limit.", "type": "number" }, "retention_sec": { "description": "RetentionSec is the retention window in seconds.\n0 = keep forever.", "type": "integer" }, "segment_duration": { "description": "SegmentDuration overrides the global segment length in seconds.\n0 = use default (4s).", "type": "integer" }, "storage_path": { "description": "StoragePath overrides the default DVR root directory for this stream.\n\"\" = use \"./dvr/{streamCode}\".", "type": "string" } } }, "domain.StreamStatus": { "type": "string", "enum": [ "idle", "active", "degraded", "stopped" ], "x-enum-varnames": [ "StatusIdle", "StatusActive", "StatusDegraded", "StatusStopped" ] }, "domain.ThumbnailConfig": { "type": "object", "properties": { "enabled": { "type": "boolean" }, "height": { "type": "integer" }, "interval_sec": { "description": "IntervalSec generates one thumbnail every N seconds.", "type": "integer" }, "output_dir": { "description": "OutputDir is relative to the publisher HLS directory.\nE.g. \"thumbnails\" → written to {hls_dir}/{stream_code}/thumbnails/thumb.jpg", "type": "string" }, "quality": { "description": "Quality is the JPEG quality (1–31, lower = better). Default: 5.", "type": "integer" }, "width": { "description": "Width and Height of the output thumbnail in pixels.\n0 = match source resolution.", "type": "integer" } } }, "domain.TranscoderConfig": { "type": "object", "properties": { "audio": { "$ref": "#/definitions/domain.AudioTranscodeConfig" }, "decoder": { "$ref": "#/definitions/domain.DecoderConfig" }, "extra_args": { "description": "ExtraArgs are raw FFmpeg arguments appended after the generated command.\nUse with caution — may conflict with generated arguments.", "type": "array", "items": { "type": "string" } }, "global": { "$ref": "#/definitions/domain.TranscoderGlobalConfig" }, "mode": { "description": "Mode selects the FFmpeg process topology. Empty = TranscoderModeMulti.", "allOf": [ { "$ref": "#/definitions/domain.TranscoderMode" } ] }, "video": { "$ref": "#/definitions/domain.VideoTranscodeConfig" } } }, "domain.TranscoderGlobalConfig": { "type": "object", "properties": { "deviceid": { "description": "DeviceID selects hardware device index.", "type": "integer" }, "fps": { "description": "FPS sets output framerate. 0 = source/default.", "type": "integer" }, "gop": { "description": "GOP sets keyframe interval in frames. 0 = encoder default.", "type": "integer" }, "hw": { "description": "HW selects the acceleration backend.", "allOf": [ { "$ref": "#/definitions/domain.HWAccel" } ] } } }, "domain.TranscoderMode": { "type": "string", "enum": [ "multi", "per_profile" ], "x-enum-varnames": [ "TranscoderModeMulti", "TranscoderModePerProfile" ] }, "domain.VODMount": { "type": "object", "properties": { "comment": { "type": "string" }, "name": { "type": "string" }, "storage": { "type": "string" } } }, "domain.VideoCodec": { "type": "string", "enum": [ "h264", "h265", "av1", "mp2v", "copy" ], "x-enum-comments": { "VideoCodecAV1": "royalty-free, best compression (high CPU)", "VideoCodecCopy": "passthrough — no re-encode", "VideoCodecH264": "AVC — widest device support", "VideoCodecH265": "HEVC — ~50% smaller than H.264", "VideoCodecMPEG2": "MPEG-2 Part 2 — DVB legacy contribution feeds, transmitter chains" }, "x-enum-descriptions": [ "AVC — widest device support", "HEVC — ~50% smaller than H.264", "royalty-free, best compression (high CPU)", "MPEG-2 Part 2 — DVB legacy contribution feeds, transmitter chains", "passthrough — no re-encode" ], "x-enum-varnames": [ "VideoCodecH264", "VideoCodecH265", "VideoCodecAV1", "VideoCodecMPEG2", "VideoCodecCopy" ] }, "domain.VideoProfile": { "type": "object", "properties": { "bframes": { "description": "Bframes is the number of consecutive B-frames the encoder may emit.\nnil = encoder default; 0 = explicit none (low-latency live);\n2-3 = typical VOD; NVENC HW B-ref pyramid is auto-enabled when \u003e0.", "type": "integer" }, "bitrate": { "description": "Bitrate is the target video bitrate in kbps. 0 = encoder auto.", "type": "integer" }, "codec": { "$ref": "#/definitions/domain.VideoCodec" }, "framerate": { "description": "Framerate is the output frame rate (fps). 0 = match source.", "type": "number" }, "height": { "type": "integer" }, "keyframe_interval": { "description": "KeyframeInterval is the GOP size in seconds.\nMust match or be a multiple of the HLS/DASH segment duration.", "type": "integer" }, "level": { "description": "Level controls the H.264/H.265 encoding level.\nCommon: \"3.1\", \"4.0\", \"4.1\", \"4.2\", \"5.0\", \"5.1\"", "type": "string" }, "max_bitrate": { "description": "MaxBitrate caps the peak bitrate in kbps (CBR/VBR ceiling). 0 = no cap.", "type": "integer" }, "preset": { "description": "Preset controls the encoder speed/quality tradeoff.\nlibx264: \"ultrafast\" | \"superfast\" | \"veryfast\" | \"faster\" | \"fast\" | \"medium\" | \"slow\" | \"veryslow\"\nNVENC: \"p1\" (fastest) .. \"p7\" (highest quality)", "type": "string" }, "profile": { "description": "Profile controls the H.264/H.265 encoding profile.\n\"baseline\" | \"main\" | \"high\" (H.264); \"main\" | \"main10\" (H.265)", "type": "string" }, "refs": { "description": "Refs is the number of reference frames. nil = encoder default.\nHigher = better compression at cost of CPU/latency. NVENC has its own caps.", "type": "integer" }, "resize_mode": { "description": "ResizeMode chooses how the source is fitted to Width/Height.\n\"\" defaults to ResizeModePad.", "allOf": [ { "$ref": "#/definitions/domain.ResizeMode" } ] }, "sar": { "description": "SAR is the output Sample Aspect Ratio, \"N:M\". \"\" = inherit from source.\nUse \"1:1\" for square pixels (web); \"16:11\", \"59:54\" etc. for anamorphic.", "type": "string" }, "width": { "description": "Width and Height define the output resolution.\nSet to 0 to keep the source dimensions (Width=0 \u0026 Height=0 = no scaling).", "type": "integer" } } }, "domain.VideoTranscodeConfig": { "type": "object", "properties": { "copy": { "description": "Copy copies origin video without re-encoding.", "type": "boolean" }, "interlace": { "description": "Interlace selects the deinterlace pre-filter applied before scaling.\nApplies once per FFmpeg subprocess (i.e. per profile in the ABR ladder).\n\"\" disables the filter; use ResizeModeProgressive to assert progressive source.", "allOf": [ { "$ref": "#/definitions/domain.InterlaceMode" } ] }, "profiles": { "description": "Profiles defines ABR renditions when re-encoding.", "type": "array", "items": { "$ref": "#/definitions/domain.VideoProfile" } } } }, "domain.WatermarkAsset": { "type": "object", "properties": { "content_type": { "description": "ContentType is the MIME type sniffed at upload time\n(e.g. \"image/png\", \"image/jpeg\"). Used for Content-Type on /raw GET.", "type": "string" }, "file_name": { "description": "FileName is the original filename at upload time, preserved for\naudit / download responses. Includes extension (e.g. \"logo.png\").", "type": "string" }, "id": { "description": "ID is the immutable filesystem-safe identifier (also the on-disk basename).", "type": "string" }, "name": { "description": "Name is the human display label shown in UIs. Defaults to the upload's\noriginal filename when the operator doesn't override.", "type": "string" }, "size_bytes": { "description": "SizeBytes is the on-disk size of the image. Sidecar JSON is excluded.", "type": "integer" }, "uploaded_at": { "description": "UploadedAt is the wall-clock UTC time the asset was first stored.", "type": "string" } } }, "domain.WatermarkConfig": { "type": "object", "properties": { "asset_id": { "description": "AssetID, when set, references a WatermarkAsset uploaded via the\n/watermarks API. Coordinator resolves it to an on-disk file path\nbefore passing the config to the transcoder. Takes precedence over\nImagePath when both are set.", "type": "string" }, "enabled": { "type": "boolean" }, "font_color": { "description": "FontColor in FFmpeg color syntax. E.g. \"white\", \"#FFFFFF\", \"white@0.8\".", "type": "string" }, "font_file": { "description": "FontFile is the path to a .ttf/.otf font file.\n\"\" = FFmpeg default font.", "type": "string" }, "font_size": { "description": "FontSize in pixels. Default: 24.", "type": "integer" }, "image_path": { "description": "ImagePath is the absolute path to a watermark image (PNG with alpha\nrecommended). Use this for assets pre-staged on the host outside the\n/watermarks library. Mutually exclusive with AssetID.", "type": "string" }, "offset_x": { "description": "OffsetX and OffsetY are pixel offsets from the chosen position edge.\nIgnored when Position == custom.", "type": "integer" }, "offset_y": { "type": "integer" }, "opacity": { "description": "Opacity controls transparency: 0.0 = fully transparent, 1.0 = fully opaque.", "type": "number" }, "position": { "description": "Position selects how the (X, Y) of the overlay are computed:\n - top_left/top_right/bottom_left/bottom_right/center: convenience\n anchors. OffsetX/Y act as inward padding from the chosen edge\n (Center ignores offsets).\n - custom: X / Y below are used as raw FFmpeg expressions —\n pixel ints (\"100\"), expressions (\"main_w-overlay_w-50\"), or\n time-aware fades (\"if(gt(t,5),10,-100)\") all work.", "allOf": [ { "$ref": "#/definitions/domain.WatermarkPosition" } ] }, "resize": { "description": "Resize, when true, makes the watermark render at a consistent on-screen\nratio across every rendition in an ABR ladder. The largest profile\nrenders the asset at its NATIVE pixel size (operators design at the top\nrendition); smaller profiles shrink the asset by the ratio of their\nwidth to the largest profile's width. Pixel-scale fields (FontSize for\ntext, OffsetX/OffsetY for both) shrink with the same factor so corner\npadding and glyph height stay visually proportional.\n\nWhen false (default), the watermark uses native pixel dimensions and\nfixed offsets on every profile — appearing larger on lower-resolution\nrenditions because they cover fewer pixels of frame.", "type": "boolean" }, "text": { "description": "Text is the string to render. Supports strftime directives for live timestamps.\nE.g. \"LIVE %{localtime:%H:%M:%S}\"", "type": "string" }, "type": { "$ref": "#/definitions/domain.WatermarkType" }, "x": { "description": "X / Y are raw FFmpeg coordinate expressions used only when Position\n== custom. Empty string defaults to \"0\". The exact variables exposed\ndepend on the filter:\n - drawtext (text watermark): w/h = frame size, tw/th = text size\n - overlay (image watermark): W/H = main video size, w/h = overlay size", "type": "string" }, "y": { "type": "string" } } }, "domain.WatermarkPosition": { "type": "string", "enum": [ "top_left", "top_right", "bottom_left", "bottom_right", "center", "custom", "bottom_right" ], "x-enum-varnames": [ "WatermarkTopLeft", "WatermarkTopRight", "WatermarkBottomLeft", "WatermarkBottomRight", "WatermarkCenter", "WatermarkCustom", "defaultWatermarkPosition" ] }, "domain.WatermarkType": { "type": "string", "enum": [ "text", "image" ], "x-enum-varnames": [ "WatermarkTypeText", "WatermarkTypeImage" ] }, "handler.configDefaultsResponse": { "type": "object", "properties": { "buffer": { "type": "object", "properties": { "capacity": { "type": "integer" } } }, "dvr": { "type": "object", "properties": { "segment_duration": { "description": "SegmentDuration is in seconds.", "type": "integer" }, "storage_path_template": { "description": "StoragePathTemplate uses {streamCode} as the placeholder for\nthe stream code — frontend must substitute it client-side.", "type": "string" } } }, "hook": { "type": "object", "properties": { "batch_flush_interval_sec": { "type": "integer" }, "batch_max_items": { "description": "Batch* defaults apply to HTTP hooks only — file hooks always\nwrite one event per line. The dispatcher's mergeBatchConfig\nuses the same Default* constants, so the API surface and the\nruntime fallback can never drift.", "type": "integer" }, "batch_max_queue_items": { "type": "integer" }, "max_retries": { "type": "integer" }, "timeout_sec": { "type": "integer" } } }, "ingestor": { "type": "object", "properties": { "hls_max_segment_buffer": { "type": "integer" }, "hls_playlist_timeout_sec": { "type": "integer" }, "hls_segment_timeout_sec": { "type": "integer" }, "rtmp_timeout_sec": { "description": "Per-protocol pull timeouts the server fills when a stream's\n`input.net.timeout_sec` is left zero. UI uses these as the\nplaceholder value on the per-input Timeout (s) field, picking\nthe right one based on the input URL scheme.", "type": "integer" }, "rtsp_timeout_sec": { "type": "integer" } } }, "listeners": { "type": "object", "properties": { "rtmp": { "$ref": "#/definitions/handler.listenerDefaults" }, "rtsp": { "$ref": "#/definitions/handler.rtspListenerDefaults" }, "srt": { "$ref": "#/definitions/handler.srtListenerDefaults" } } }, "manager": { "type": "object", "properties": { "input_packet_timeout_sec": { "type": "integer" } } }, "publisher": { "type": "object", "properties": { "dash": { "$ref": "#/definitions/handler.liveSegmentDefaults" }, "hls": { "$ref": "#/definitions/handler.liveSegmentDefaults" } } }, "push": { "type": "object", "properties": { "retry_timeout_sec": { "type": "integer" }, "timeout_sec": { "type": "integer" } } }, "transcoder": { "type": "object", "properties": { "audio": { "type": "object", "properties": { "bitrate_k": { "type": "integer" }, "codec": { "$ref": "#/definitions/domain.AudioCodec" } } }, "ffmpeg_path": { "type": "string" }, "global": { "type": "object", "properties": { "deviceid": { "type": "integer" }, "hw": { "$ref": "#/definitions/domain.HWAccel" } } }, "mode": { "description": "Mode is the per-stream FFmpeg topology default applied when\nStream.Transcoder.Mode is left empty. UI uses this as the\nplaceholder for the mode selector — operators see the real\napplied default (\"multi\") instead of generic \"default\" text.", "allOf": [ { "$ref": "#/definitions/domain.TranscoderMode" } ] }, "video": { "type": "object", "properties": { "bitrate_k": { "type": "integer" }, "codec": { "description": "Codec is the codec family used when the user leaves\nVideoProfile.Codec empty. EncoderByCodecHW resolves it (or\nany explicit codec) to the actual FFmpeg encoder name based\non the stream's Global.HW selection — frontend looks up\n`EncoderByCodecHW[codec][hw]` to render the placeholder.", "type": "string" }, "encoder_by_codec_hw": { "type": "object", "additionalProperties": { "type": "object", "additionalProperties": { "type": "string" } } }, "resize_mode": { "$ref": "#/definitions/domain.ResizeMode" } } } } } } }, "handler.listenerDefaults": { "type": "object", "properties": { "listen_host": { "type": "string" }, "port": { "type": "integer" } } }, "handler.liveSegmentDefaults": { "type": "object", "properties": { "live_ephemeral": { "type": "boolean" }, "live_history": { "type": "integer" }, "live_segment_sec": { "type": "integer" }, "live_window": { "type": "integer" } } }, "handler.probeRequest": { "type": "object", "properties": { "ffmpeg_path": { "type": "string" } } }, "handler.rtspListenerDefaults": { "type": "object", "properties": { "listen_host": { "type": "string" }, "port": { "type": "integer" }, "transport": { "type": "string" } } }, "handler.srtListenerDefaults": { "type": "object", "properties": { "latency_ms": { "type": "integer" }, "listen_host": { "type": "string" }, "port": { "type": "integer" } } }, "manager.InputHealthSnapshot": { "type": "object", "properties": { "bitrate_kbps": { "type": "integer" }, "errors": { "type": "array", "items": { "$ref": "#/definitions/domain.ErrorEntry" } }, "input_priority": { "type": "integer" }, "last_packet_at": { "type": "string" }, "packet_loss": { "type": "number" }, "status": { "$ref": "#/definitions/domain.StreamStatus" }, "tracks": { "type": "array", "items": { "$ref": "#/definitions/domain.MediaTrackInfo" } } } }, "manager.MediaSummary": { "type": "object", "properties": { "input_bitrate_kbps": { "type": "integer" }, "inputs": { "type": "array", "items": { "$ref": "#/definitions/domain.MediaTrackInfo" } }, "output_bitrate_kbps": { "type": "integer" }, "outputs": { "type": "array", "items": { "$ref": "#/definitions/domain.MediaTrackInfo" } } } }, "manager.RuntimeStatus": { "type": "object", "properties": { "active_input_priority": { "type": "integer" }, "exhausted": { "type": "boolean" }, "inputs": { "type": "array", "items": { "$ref": "#/definitions/manager.InputHealthSnapshot" } }, "media": { "description": "Media is the UI-friendly summary of the current input → output track\nshape (what the dashboard renders as \"Input media info / Output media\ninfo / 954kbit/s -\u003e 2577kbit/s\"). Populated by the API handler — the\nmanager doesn't know the output config.", "allOf": [ { "$ref": "#/definitions/manager.MediaSummary" } ] }, "override_input_priority": { "type": "integer" }, "pipeline_active": { "type": "boolean" }, "publisher": { "$ref": "#/definitions/publisher.RuntimeStatus" }, "status": { "$ref": "#/definitions/domain.StreamStatus" }, "switches": { "description": "Switches is the rolling history of active-input changes (newest at\nindex 0, capped at maxSwitchHistory). Stream-level — switches happen\nBETWEEN inputs, so this lives next to Inputs rather than inside one.", "type": "array", "items": { "$ref": "#/definitions/manager.SwitchEvent" } }, "transcoder": { "$ref": "#/definitions/transcoder.RuntimeStatus" } } }, "manager.SwitchEvent": { "type": "object", "properties": { "at": { "type": "string" }, "detail": { "description": "Detail is human-readable extra context (error message, timeout\nduration, …). Empty for reasons that have no extra context (manual,\nfailback, input_added, input_removed).", "type": "string" }, "from": { "type": "integer" }, "reason": { "$ref": "#/definitions/manager.SwitchReason" }, "to": { "type": "integer" } } }, "manager.SwitchReason": { "type": "string", "enum": [ "initial", "error", "timeout", "manual", "failback", "recovery", "input_added", "input_removed" ], "x-enum-varnames": [ "SwitchReasonInitial", "SwitchReasonError", "SwitchReasonTimeout", "SwitchReasonManual", "SwitchReasonFailback", "SwitchReasonRecovery", "SwitchReasonInputAdded", "SwitchReasonInputRemoved" ] }, "publisher.PushSnapshot": { "type": "object", "properties": { "attempt": { "type": "integer" }, "connected_at": { "type": "string" }, "errors": { "type": "array", "items": { "$ref": "#/definitions/domain.ErrorEntry" } }, "status": { "$ref": "#/definitions/publisher.PushStatus" }, "url": { "type": "string" } } }, "publisher.PushStatus": { "type": "string", "enum": [ "starting", "active", "reconnecting", "failed" ], "x-enum-varnames": [ "PushStatusStarting", "PushStatusActive", "PushStatusReconnecting", "PushStatusFailed" ] }, "publisher.RuntimeStatus": { "type": "object", "properties": { "pushes": { "type": "array", "items": { "$ref": "#/definitions/publisher.PushSnapshot" } } } }, "transcoder.ProbeResult": { "type": "object", "properties": { "encoders": { "type": "object", "additionalProperties": { "type": "object", "additionalProperties": { "type": "boolean" } } }, "errors": { "type": "array", "items": { "type": "string" } }, "filters": { "type": "object", "additionalProperties": { "type": "boolean" } }, "muxers": { "type": "object", "additionalProperties": { "type": "boolean" } }, "ok": { "type": "boolean" }, "path": { "type": "string" }, "version": { "type": "string" }, "warnings": { "type": "array", "items": { "type": "string" } } } }, "transcoder.ProfileSnapshot": { "type": "object", "properties": { "errors": { "type": "array", "items": { "$ref": "#/definitions/domain.ErrorEntry" } }, "index": { "description": "0-based ladder index; track label = track_\u003cindex+1\u003e", "type": "integer" }, "restart_count": { "type": "integer" }, "track": { "type": "string" } } }, "transcoder.RuntimeStatus": { "type": "object", "properties": { "profiles": { "type": "array", "items": { "$ref": "#/definitions/transcoder.ProfileSnapshot" } } } }, "version.Info": { "type": "object", "properties": { "built_at": { "type": "string" }, "commit": { "type": "string" }, "version": { "type": "string" } } }, "vod.FileEntry": { "type": "object", "properties": { "ingest_url": { "type": "string" }, "is_dir": { "type": "boolean" }, "mod_time_unix": { "type": "integer" }, "name": { "type": "string" }, "path": { "description": "path relative to the mount root, with forward slashes", "type": "string" }, "play_url": { "type": "string" }, "size": { "type": "integer" } } } } }