{ "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": "P0 stub — always returns ok=true. Real capability probe will be reintroduced once the native libav pipeline lands.", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "system" ], "summary": "Probe transcoder capabilities (stub during native migration).", "responses": { "200": { "description": "OK", "schema": { "$ref": "#/definitions/handler.probeStubResponse" } }, "400": { "description": "Bad Request", "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" } } } } }, "/policies": { "get": { "produces": [ "application/json" ], "tags": [ "policies" ], "summary": "List media-auth policies", "responses": { "200": { "description": "OK", "schema": { "type": "object", "additionalProperties": true } }, "500": { "description": "Internal Server Error", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } } } } }, "/policies/{code}": { "get": { "produces": [ "application/json" ], "tags": [ "policies" ], "summary": "Get media-auth policy", "parameters": [ { "type": "string", "description": "Policy code", "name": "code", "in": "path", "required": true } ], "responses": { "200": { "description": "OK", "schema": { "type": "object", "additionalProperties": true } }, "404": { "description": "Not Found", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } } } }, "post": { "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "policies" ], "summary": "Create or update media-auth policy", "parameters": [ { "type": "string", "description": "Policy code", "name": "code", "in": "path", "required": true }, { "description": "Policy configuration", "name": "body", "in": "body", "required": true, "schema": { "$ref": "#/definitions/domain.Policy" } } ], "responses": { "200": { "description": "OK", "schema": { "type": "object", "additionalProperties": true } }, "201": { "description": "Created", "schema": { "type": "object", "additionalProperties": true } }, "400": { "description": "Bad Request", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } }, "500": { "description": "Internal Server Error", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } } } }, "delete": { "produces": [ "application/json" ], "tags": [ "policies" ], "summary": "Delete media-auth policy", "parameters": [ { "type": "string", "description": "Policy code", "name": "code", "in": "path", "required": true } ], "responses": { "204": { "description": "No Content" }, "404": { "description": "Not Found", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } }, "409": { "description": "Conflict", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } }, "500": { "description": "Internal Server Error", "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" } } } } }, "/sessions": { "get": { "produces": [ "application/json" ], "tags": [ "sessions" ], "summary": "List play sessions", "parameters": [ { "type": "string", "description": "Filter by stream code", "name": "stream", "in": "query" }, { "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" } }, "400": { "description": "Bad Request", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } } } } }, "/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}/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}/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" } } } } }, "/templates": { "get": { "produces": [ "application/json" ], "tags": [ "templates" ], "summary": "List templates", "responses": { "200": { "description": "OK", "schema": { "type": "object", "additionalProperties": true } }, "500": { "description": "Internal Server Error", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } } } } }, "/templates/{code}": { "get": { "produces": [ "application/json" ], "tags": [ "templates" ], "summary": "Get template", "parameters": [ { "type": "string", "description": "Template code", "name": "code", "in": "path", "required": true } ], "responses": { "200": { "description": "OK", "schema": { "type": "object", "additionalProperties": true } }, "404": { "description": "Not Found", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } } } }, "post": { "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "templates" ], "summary": "Create or update template", "parameters": [ { "type": "string", "description": "Template code", "name": "code", "in": "path", "required": true }, { "description": "Template configuration", "name": "body", "in": "body", "required": true, "schema": { "$ref": "#/definitions/domain.Template" } } ], "responses": { "200": { "description": "OK", "schema": { "type": "object", "additionalProperties": true } }, "201": { "description": "Created", "schema": { "type": "object", "additionalProperties": true } }, "400": { "description": "Bad Request", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } }, "500": { "description": "Internal Server Error", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } } } }, "delete": { "produces": [ "application/json" ], "tags": [ "templates" ], "summary": "Delete template", "parameters": [ { "type": "string", "description": "Template code", "name": "code", "in": "path", "required": true } ], "responses": { "204": { "description": "No Content" }, "404": { "description": "Not Found", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } }, "409": { "description": "Conflict", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } }, "500": { "description": "Internal Server Error", "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 } ], "responses": { "201": { "description": "Created", "schema": { "$ref": "#/definitions/apidocs.WatermarkAssetData" } }, "400": { "description": "Bad Request", "schema": { "$ref": "#/definitions/apidocs.ErrorBody" } }, "409": { "description": "Conflict", "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/{filename}": { "get": { "produces": [ "application/json" ], "tags": [ "watermarks" ], "summary": "Get watermark metadata", "parameters": [ { "type": "string", "description": "Asset filename (e.g. logo.png)", "name": "filename", "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 filename", "name": "filename", "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/{filename}/raw": { "get": { "produces": [ "image/png" ], "tags": [ "watermarks" ], "summary": "Download watermark image", "parameters": [ { "type": "string", "description": "Asset filename", "name": "filename", "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.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" }, "playback_policy": { "description": "PlaybackPolicy binds this stream to a named media-auth Policy by its\ncode (see domain.Policy). Empty means no policy — the stream is public\n(allow-all). The referenced policy carries the full rule set (token\nrequirement + secret, IP/country/UA/domain allow-deny lists). See\ninternal/mediaauth.", "type": "string" }, "protocols": { "description": "Protocols defines which delivery protocols are opened for this stream.\nnil means the field is unset and ResolveStream inherits the template's\nProtocols (or leaves the resolved value nil when no template applies —\npublisher treats nil as \"no protocols enabled\"). An explicit non-nil\npointer — including the zero value \u0026OutputProtocols{} — is an\noperator-asserted override and beats template inheritance.", "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 (social media live ingest, 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" } }, "template": { "description": "Template references a Template by its code; the template's config-like\nfields fill in any field this stream leaves at its zero value. nil\nmeans no template inheritance — the stream stands alone. See\nResolveStream in template.go for the merge rules.", "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.APIAuthConfig": { "type": "object", "properties": { "enabled": { "type": "boolean" }, "users": { "type": "array", "items": { "$ref": "#/definitions/config.APIUser" } } } }, "config.APIUser": { "type": "object", "properties": { "password_hash": { "type": "string" }, "role": { "type": "string" }, "username": { "type": "string" } } }, "config.AuthConfig": { "type": "object", "properties": { "api": { "$ref": "#/definitions/config.APIAuthConfig" } } }, "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" }, "file_root_dir": { "description": "FileRootDir confines file-type hooks to this directory (S-2). When set,\na file hook's target must resolve inside it — preventing arbitrary\nhost-file create/append (e.g. clobbering the store, filling disk). Empty\nkeeps the legacy \"any absolute path\" behaviour for backward compatibility;\noperators are encouraged to set it (e.g. /var/log/open-streamer/hooks).", "type": "string" }, "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" }, "max_playback_conn_per_stream": { "description": "MaxPlaybackConnPerStream / MaxPlaybackConnTotal cap concurrent RTSP /\nRTMP / SRT / HTTP-MPEGTS playback connections (per stream and globally)\nto bound the heavyweight per-connection state a flood can allocate (A-1).\nUnset (0) applies a protective default; a negative value means unlimited.\nHLS/DASH viewers go over stateless HTTP and are not counted here.", "type": "integer" }, "max_playback_conn_total": { "type": "integer" } } }, "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.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" }, "volume": { "description": "Volume sets the output audio gain on the re-encode path\n(Copy=false; passthrough audio cannot be gained). The value is either a\nplain linear multiplier (\"2\", \"0.5\" → output = value × input) or a\ndecibel string (\"+9dB\", \"-6dB\" → output level = input ± dB). Both reduce\nto one linear factor via ParseAudioVolume. Empty / \"1\" / \"0dB\" = unity\n(no change). Boosting can clip; samples are clamped to full scale.", "type": "string" } } }, "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.updated", "stream.started", "stream.stopped", "stream.deleted", "input.connected", "input.reconnecting", "input.degraded", "input.failed", "input.failover", "input.recovered", "recording.started", "recording.stopped", "recording.failed", "segment.written", "dvr.segment_pruned", "transcoder.started", "transcoder.stopped", "transcoder.error", "push.started", "push.active", "push.reconnecting", "push.failed", "config.changed", "watermark.asset_created", "watermark.asset_deleted", "hook.created", "hook.updated", "hook.deleted", "template.created", "template.updated", "template.deleted", "policy.created", "policy.updated", "policy.deleted", "stream.runtime_created", "stream.runtime_expired", "session.opened", "session.closed" ], "x-enum-comments": { "EventDVRSegmentPruned": "retention loop deleted an aged-out segment", "EventInputConnected": "source connected successfully", "EventInputDegraded": "error detected by manager", "EventInputFailed": "worker exited / non-retriable", "EventInputFailover": "switched to a different input", "EventInputReconnecting": "transient error, retrying", "EventInputRecovered": "previously degraded input is healthy again (auto or via failback probe)", "EventPushActive": "handshake ok; media flowing", "EventPushFailed": "dest.Limit exhausted or terminal error", "EventPushReconnecting": "write fail or input discontinuity, retrying", "EventPushStarted": "dialing target, pre-handshake", "EventStreamUpdated": "PUT /streams/{code} on existing record" }, "x-enum-descriptions": [ "", "PUT /streams/{code} on existing record", "", "", "", "source connected successfully", "transient error, retrying", "error detected by manager", "worker exited / non-retriable", "switched to a different input", "previously degraded input is healthy again (auto or via failback probe)", "", "", "", "", "retention loop deleted an aged-out segment", "", "", "", "dialing target, pre-handshake", "handshake ok; media flowing", "write fail or input discontinuity, retrying", "dest.Limit exhausted or terminal error", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" ], "x-enum-varnames": [ "EventStreamCreated", "EventStreamUpdated", "EventStreamStarted", "EventStreamStopped", "EventStreamDeleted", "EventInputConnected", "EventInputReconnecting", "EventInputDegraded", "EventInputFailed", "EventInputFailover", "EventInputRecovered", "EventRecordingStarted", "EventRecordingStopped", "EventRecordingFailed", "EventSegmentWritten", "EventDVRSegmentPruned", "EventTranscoderStarted", "EventTranscoderStopped", "EventTranscoderError", "EventPushStarted", "EventPushActive", "EventPushReconnecting", "EventPushFailed", "EventConfigChanged", "EventWatermarkAssetCreated", "EventWatermarkAssetDeleted", "EventHookCreated", "EventHookUpdated", "EventHookDeleted", "EventTemplateCreated", "EventTemplateUpdated", "EventTemplateDeleted", "EventPolicyCreated", "EventPolicyUpdated", "EventPolicyDeleted", "EventStreamRuntimeCreated", "EventStreamRuntimeExpired", "EventSessionOpened", "EventSessionClosed" ] }, "domain.GlobalConfig": { "type": "object", "properties": { "auth": { "$ref": "#/definitions/config.AuthConfig" }, "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" }, "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 the playlist request carried any timeshift query\nparameter (from / delay / dur / ago) — false for live-edge hits. Set\nby sessions.HTTPMiddleware after detecting the query shape. 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.Policy": { "type": "object", "properties": { "allow_countries": { "type": "array", "items": { "type": "string" } }, "allow_ips": { "description": "Static allow/deny chain. IPs accept exact addresses or CIDR ranges;\nCountries are ISO 3166-1 alpha-2 codes (need a GeoIP DB — see\nSessionsConfig.GeoIPDBPath); UserAgents match case-insensitive substring;\nAllowedDomains match the Referer host (exact or parent domain).", "type": "array", "items": { "type": "string" } }, "allow_user_agents": { "type": "array", "items": { "type": "string" } }, "allowed_domains": { "type": "array", "items": { "type": "string" } }, "code": { "description": "Code is the unique key chosen by the operator.", "type": "string" }, "deny_countries": { "type": "array", "items": { "type": "string" } }, "deny_ips": { "type": "array", "items": { "type": "string" } }, "deny_user_agents": { "type": "array", "items": { "type": "string" } }, "description": { "type": "string" }, "name": { "description": "Name and Description are operator-facing metadata.", "type": "string" }, "require_token": { "description": "RequireToken makes a valid signed playback token mandatory. The server\nonly VERIFIES tokens; clients mint them with TokenSecret — see\ninternal/mediaauth.SignToken for the canonical token format.", "type": "boolean" }, "token_secret": { "description": "TokenSecret is this policy's HMAC-SHA256 verification key. Required when\nRequireToken is true. Each policy owns its own secret so revoking one\npolicy's key never affects another.", "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://rtmp.example.com/live2/{key})\n rtmps:// — TLS-wrapped RTMP, default port 443 (e.g. rtmps://rtmps.example.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.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", "max_lifetime", "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", "SessionCloseMaxLifetime": "total lifetime exceeded the configured cap", "SessionCloseShutdown": "server shutting down" }, "x-enum-descriptions": [ "no activity within the configured idle window", "total lifetime exceeded the configured cap", "TCP/UDP peer closed (RTMP/SRT/RTSP)", "server shutting down", "operator force-closed via API" ], "x-enum-varnames": [ "SessionCloseIdle", "SessionCloseMaxLifetime", "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" }, "profiles": { "description": "Profiles selects which renditions the CMAF blob archive records:\n\"\" or \"best\" = the best rendition only (default); \"all\" = every rendition\nin the ABR ladder.", "type": "string" }, "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.Template": { "type": "object", "properties": { "code": { "description": "Code is the unique key chosen by the operator.", "type": "string" }, "description": { "type": "string" }, "dvr": { "description": "DVR overrides the global DVR settings. Streams that inherit this\ntemplate inherit the DVR configuration unless they set their own.", "allOf": [ { "$ref": "#/definitions/domain.StreamDVRConfig" } ] }, "inputs": { "description": "Inputs are inherited by streams that leave Inputs empty. A template\ncarrying a publish:// input is also the trigger that makes Prefixes\nmatching produce a runtime stream — without an input source there\nis nothing for the runtime stream to subscribe to.", "type": "array", "items": { "$ref": "#/definitions/domain.Input" } }, "name": { "description": "Name and Description are template-level metadata. Name surfaces in\nthe API for human-readable lists; Description carries the rationale\nbehind the template's settings. Streams inheriting this template\nkeep their own Name / Description fields — the template metadata is\nfor operator-facing tooling, not for downstream consumers.", "type": "string" }, "playback_policy": { "description": "PlaybackPolicy is the media-auth Policy code inherited by streams\nreferencing this template (see domain.Policy). Empty = no policy\n(inheriting streams stay public unless they set their own).", "type": "string" }, "prefixes": { "description": "Prefixes is the list of URL-path prefixes that trigger auto-publish.\nWhen an encoder pushes to a path whose first segment(s) match any\nprefix here AND this template has at least one publish:// input, a\nruntime stream is created on the fly. Prefixes must not overlap any\nother template's prefix (validated at save time).", "type": "array", "items": { "type": "string" } }, "protocols": { "description": "Protocols defines which delivery protocols are opened. nil means the\ntemplate doesn't dictate protocols — inheriting streams must declare\ntheir own, or the resolved value stays nil (no protocols enabled).", "allOf": [ { "$ref": "#/definitions/domain.OutputProtocols" } ] }, "push": { "description": "Push is the list of external destinations the server actively pushes to.", "type": "array", "items": { "$ref": "#/definitions/domain.PushDestination" } }, "stream_key": { "description": "StreamKey is the shared push-authentication secret for streams that\ninherit this template. Empty means no secret is templated and each\nstream may set its own (or none).", "type": "string" }, "tags": { "description": "Tags propagate to inheriting streams when the stream leaves Tags\nempty. Useful for grouping every stream that follows a common\nprofile under one operational label.", "type": "array", "items": { "type": "string" } }, "thumbnail": { "description": "Thumbnail controls periodic screenshot generation.", "allOf": [ { "$ref": "#/definitions/domain.ThumbnailConfig" } ] }, "transcoder": { "description": "Transcoder controls encoding/decoding settings. nil means no\ntranscoding for streams inheriting this template (unless they set\ntheir own Transcoder).", "allOf": [ { "$ref": "#/definitions/domain.TranscoderConfig" } ] }, "watermark": { "description": "Watermark is an optional text or image overlay applied before encoding.", "allOf": [ { "$ref": "#/definitions/domain.WatermarkConfig" } ] } } }, "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" }, "global": { "$ref": "#/definitions/domain.TranscoderGlobalConfig" }, "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.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 in the transcoder's video pipeline, before scaling.\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 via http.DetectContentType from\nthe first 512 bytes. Used as the Content-Type response header on /raw.", "type": "string" }, "filename": { "description": "Filename is the on-disk basename, ALSO the stable identifier used\nby Stream.Watermark.Filename and every REST URL.", "type": "string" }, "size_bytes": { "description": "SizeBytes is the on-disk size of the image (from os.Stat).", "type": "integer" }, "uploaded_at": { "description": "UploadedAt is the file's modification time in UTC. Save sets it to the\nwall clock at upload; subsequent rebuilds derive it from mtime so the\nlist survives restart without an external metadata store.", "type": "string" } } }, "domain.WatermarkConfig": { "type": "object", "properties": { "enabled": { "type": "boolean" }, "filename": { "description": "Filename references a WatermarkAsset by its on-disk name in the\n/watermarks library (eg. `vtv1_logo.png`). Required when Type ==\nWatermarkTypeImage. The coordinator resolves this into ImagePath\n(private field) before passing the config to the transcoder.", "type": "string" }, "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" }, "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" } } }, "global": { "type": "object", "properties": { "deviceid": { "type": "integer" }, "hw": { "$ref": "#/definitions/domain.HWAccel" } } }, "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.probeStubResponse": { "type": "object", "properties": { "notice": { "type": "string" }, "ok": { "type": "boolean" } } }, "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" }, "started_at": { "description": "StartedAt is the wallclock moment the pipeline went live. Populated\nby the stream handler from coordinator.StreamStartedAt; nil for a\nstopped stream so the UI can show \"—\" instead of an epoch zero.", "type": "string" }, "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" }, "uptime_sec": { "description": "UptimeSec is the precomputed elapsed seconds since StartedAt at the\nmoment of the response. Bundled so frontend can render uptime without\nreading wallclock + doing the subtraction itself (and getting clock-\nskew weirdness when the browser is hours off from the server).", "type": "integer" } } }, "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.ProcessStatus": { "type": "string", "enum": [ "healthy", "unhealthy" ], "x-enum-varnames": [ "ProcessStatusHealthy", "ProcessStatusUnhealthy" ] }, "transcoder.RenditionSnapshot": { "type": "object", "properties": { "index": { "type": "integer" }, "track": { "type": "string" } } }, "transcoder.RuntimeStatus": { "type": "object", "properties": { "errors": { "type": "array", "items": { "$ref": "#/definitions/domain.ErrorEntry" } }, "renditions": { "type": "array", "items": { "$ref": "#/definitions/transcoder.RenditionSnapshot" } }, "restart_count": { "type": "integer" }, "status": { "$ref": "#/definitions/transcoder.ProcessStatus" } } }, "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" } } } } }