videoserver

package module
v0.8.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 21, 2025 License: MIT Imports: 35 Imported by: 0

README

GoDoc Sourcegraph Go Report Card GitHub tag

Golang-based video-server for re-streaming RTSP to HLS/MSE

Table of Contents

About

Simple WS/HTTP server for re-streaming video (RTSP) to client in MSE/HLS format.

It is highly inspired by https://github.com/deepch and his projects. So why am I trying to reinvent the wheel? Well, I'm just trying to fit my needs.

Instalation

Binaries

Linux - link

From source
go get github.com/LdDl/video-server
# or just clone it
# git clone https://github.com/LdDl/video-server.git

Go to root folder of downloaded repository, move to cmd/video_server folder:

cd $CLONED_PATH/cmd/video_server
go build -o video_server main.go

Usage

video_server -h
-conf string
    Path to configuration either TOML-file or JSON-file (default "conf.toml")
-cpuprofile file
    write cpu profile to file
-memprofile file
    write memory profile to file
Start server

Prepare configuration file (example here). Then run binary:

video_server --conf=conf.toml
Test Client-Server

For HLS-based player go to hls-subdirectory.

For MSE-based (websockets) player go to mse-subdirectory.

Then follow this set of commands:

npm install
export NODE_OPTIONS=--openssl-legacy-provider
npm run dev

You will se something like this after succesfull fron-end start:

DONE  Compiled successfully in 1783ms                                                                                                                                                                         12:09:30 PM
App running at:
- Local:   http://localhost:8080/ 

Paste link to the browser and check if video loaded successfully.

Archive

You can configure application to write MP4 chunks of custom duration (but not less than first keyframe duration) to the filesystem or S3 MinIO. The archive system supports two independent modes: recording (writing new segments) and serving (playback of existing archive files).

Recording vs Serving

The archive configuration has two separate flags:

  • recording - Enables writing new archive segments. When true, the server will continuously record video stream chunks to storage.
  • serving - Enables playback of existing archive files via WebSocket. When true, clients can request archive playback even if recording is disabled.

This allows flexible deployment scenarios:

  • recording = true, serving = true - Full archive functionality (record and playback)
  • recording = true, serving = false - Record only, no playback API
  • recording = false, serving = true - Playback only from existing archive files (useful for read-only archive access)
  • recording = false, serving = false - Archive completely disabled
Global Configuration

Global archive settings provide defaults for all streams:

[archive]
recording = true
serving = true
directory = "./mp4"
ms_per_file = 30000
Per-Stream Configuration

Each stream can override global settings. Per-stream settings take precedence over global configuration.

For filesystem storage:

[[rtsp_streams]]
# ...
# Some other single stream props
# ...
archive = { recording = true, ms_per_file = 20000, type = "filesystem", directory = "custom_folder" }

For S3 MinIO storage:

[[rtsp_streams]]
# ...
# Some other single stream props
# ...
archive = { recording = true, ms_per_file = 20000, type = "minio", directory = "custom_folder", minio_bucket = "vod-bucket", minio_path = "/var/archive_data_custom" }
Configuration Fallback

When a client requests archive playback:

  1. The server first checks per-stream archive configuration
  2. If per-stream storage is not available, it falls back to global archive settings
  3. Global serving flag must be true for fallback to work

This allows you to:

  • Record with per-stream settings but serve using global directory
  • Serve pre-existing archive files without per-stream configuration
MinIO Configuration

For storing archive to S3 MinIO, configure both filesystem (for temporary files) and MinIO settings:

[archive]
recording = true
serving = true
directory = "./mp4"
ms_per_file = 30000
minio_settings = { host = "localhost", port = 29199, user = "minio_secret_login", password = "minio_secret_password", default_bucket = "archive-bucket", default_path = "/var/archive_data" }

To install MinIO you can use ./docker-compose.yaml or [./scripts/minio-ansible.yml](Ansible script) for example of deployment workflows.

Archive REST API

The server provides a REST API endpoint to query available archive time ranges:

GET /archive/:stream_id/ranges

Response example:

{
  "stream_id": "0742091c-19cd-4658-9b4f-5320da160f45",
  "ranges": [
    {
      "start": "2025-12-20T10:00:00Z",
      "end": "2025-12-20T12:30:00Z"
    },
    {
      "start": "2025-12-20T14:00:00Z",
      "end": "2025-12-20T18:00:00Z"
    }
  ]
}

Dependencies

GIN web-framework - https://github.com/gin-gonic/gin. License is MIT

Media library - http://github.com/deepch/vdk. License is MIT.

UUID generation and parsing - https://github.com/google/uuid. License is BSD 3-Clause

Websockets - https://github.com/gorilla/websocket. License is BSD 2-Clause

m3u8 library - https://github.com/grafov/m3u8. License is BSD 3-Clause

Working with mp4 files - https://github.com/Eyevinn/mp4ff. License is MIT

errors wrapping - https://github.com/pkg/errors . License is BSD 2-Clause

License

You can check it here

Developers

Roman - https://github.com/webver

Pavel - https://github.com/Pavel7824

Dimitrii Lopanov - https://github.com/LdDl

Morozka - https://github.com/morozka

Documentation

Index

Constants

View Source
const (
	SCOPE_APP           = "app"
	SCOPE_CONFIGURATION = "configuration"
	SCOPE_STREAM        = "stream"
	SCOPE_STREAMING     = "streaming"
	SCOPE_WS_HANDLER    = "ws_handler"
	SCOPE_API_SERVER    = "api_server"
	SCOPE_WS_SERVER     = "ws_server"
	SCOPE_ARCHIVE       = "archive"
	SCOPE_MP4           = "mp4"
	SCOPE_HLS           = "hls"

	EVENT_APP_CORS_CONFIG = "app_cors_config"

	EVENT_STREAM_CODEC_ADD     = "stream_codec_add"
	EVENT_STREAM_STATUS_UPDATE = "stream_status_update"
	EVENT_STREAM_CLIENT_ADD    = "stream_client_add"
	EVENT_STREAM_CLIENT_DELETE = "stream_client_delete"
	EVENT_STREAM_CAST_PACKET   = "stream_cast"

	EVENT_STREAMING_RUN                 = "streaming_run"
	EVENT_STREAMING_START               = "streaming_start"
	EVENT_STREAMING_DONE                = "streaming_done"
	EVENT_STREAMING_RESTART             = "streaming_restart"
	EVENT_STREAMING_DIAL                = "streaming_dial"
	EVENT_STREAMING_STATUS_UPDATE       = "streaming_status_update"
	EVENT_STREAMING_PACKET_SIGNAL       = "streaming_packet_signal"
	EVENT_STREAMING_STOP_SIGNAL         = "streaming_stop_signal"
	EVENT_STREAMING_UNKNOWN_SIGNAL      = "streaming_unknown_signal"
	EVENT_STREAMING_CODEC_UPDATE_SIGNAL = "streaming_codec_update_signal"
	EVENT_STREAMING_EXIT_SIGNAL         = "streaming_codec_exit_signal"
	EVENT_STREAMING_CODEC_MET           = "streaming_codec_met"
	EVENT_STREAMING_AUDIO_MET           = "streaming_audio_met"
	EVENT_STREAMING_HLS_CAST            = "streaming_hls_cast"
	EVENT_STREAMING_MP4_CAST            = "streaming_mp4_cast"
	EVENT_STREAMING_EOF                 = "streaming_eof"
	EVENT_STREAMING_LOOP                = "streaming_loop"
	EVENT_STREAMING_COMPLETE            = "streaming_complete"

	EVENT_API_PREPARE     = "api_server_prepare"
	EVENT_API_START       = "api_server_start"
	EVENT_API_CORS_ENABLE = "api_server_cors_enable"
	EVENT_API_REQUEST     = "api_request"

	EVENT_WS_PREPARE     = "ws_server_prepare"
	EVENT_WS_START       = "ws_server_start"
	EVENT_WS_CORS_ENABLE = "ws_server_cors_enable"
	EVENT_WS_REQUEST     = "ws_request"
	EVENT_WS_UPGRADER    = "ws_upgrader"
	EVENT_WS_PING        = "ws_ping"

	EVENT_HLS_START_CAST              = "hls_start_cast"
	EVENT_HLS_PLAYLIST_PREPARE        = "hls_playlist_prepare"
	EVENT_HLS_PLAYLIST_CREATE         = "hls_playlist_create"
	EVENT_HLS_PLAYLIST_RESTART        = "hls_playlist_restart"
	EVENT_HLS_CLOSE_FILE              = "hls_close_file"
	EVENT_HLS_WRITE_TRAIL             = "hls_write_trail"
	EVENT_HLS_REMOVE_OUTDATED         = "hls_remove_outdated"
	EVENT_HLS_REMOVE_OUTDATED_SEGMENT = "hls_remove_outdated_segment"
	EVENT_HLS_REMOVE_CHUNK            = "hls_remove_chunk"

	EVENT_ARCHIVE_START_CAST  = "archive_start_cast"
	EVENT_ARCHIVE_CREATE_FILE = "archive_create_file"
	EVENT_ARCHIVE_CLOSE_FILE  = "archive_close_file"
	EVENT_CHAN_PACKET         = "mp4_chan_pck"
	EVENT_CHAN_STOP           = "mp4_chan_stop"
	EVENT_CHAN_KEYFRAME       = "mp4_chan_keyframe"
	EVENT_SEGMENT_CUT         = "mp4_segment_cut"
	EVENT_NO_START            = "mp4_no_start"
	EVENT_MP4_WRITE           = "mp4_write"
	EVENT_MP4_WRITE_TRAIL     = "mp4_write_trail"
	EVENT_MP4_SAVE_MINIO      = "mp4_save_minio"
	EVENT_MP4_CLOSE           = "mp4_close"
)
View Source
const (
	STOP_SIGNAL_ERR = StopSignal(iota)
	STOP_SIGNAL_NO_VIDEO
	STOP_SIGNAL_DISCONNECT
	STOP_SIGNAL_STOP_DIAL
)
View Source
const (
	STREAM_TYPE_UNDEFINED = StreamType(iota)
	STREAM_TYPE_RTSP
	STREAM_TYPE_HLS
	STREAM_TYPE_MSE
	STREAM_TYPE_LOCAL_FILE
)
View Source
const (
	VERBOSE_NONE = VerboseLevel(iota)
	VERBOSE_SIMPLE
	VERBOSE_ADD
	VERBOSE_ALL
)

Variables

View Source
var (
	ErrStreamNotFound         = fmt.Errorf("stream not found for provided ID")
	ErrStreamHasNoVideo       = fmt.Errorf("stream has no video")
	ErrStreamDisconnected     = fmt.Errorf("disconnected")
	ErrStreamTypeNotExists    = fmt.Errorf("stream type does not exists")
	ErrStreamTypeNotSupported = fmt.Errorf("stream type is not supported")
	ErrNotSupportedStorage    = fmt.Errorf("not supported storage")
	ErrNullArchive            = fmt.Errorf("archive == nil")
)
View Source
var (
	ErrTimeFailure = fmt.Errorf("bad packet times")
)

Functions

func ArchiveRangesWrapper added in v0.8.0

func ArchiveRangesWrapper(app *Application, verboseLevel VerboseLevel) func(ctx *gin.Context)

ArchiveRangesWrapper returns handler for listing available archive time ranges

func ArchiveWSHandler added in v0.8.0

func ArchiveWSHandler(app *Application, wsUpgrader *websocket.Upgrader, verboseLevel VerboseLevel) func(w http.ResponseWriter, r *http.Request)

ArchiveWSHandler handles WebSocket connections for archive playback Query params: stream_id, start (unix timestamp), duration (seconds, optional) Supports chunked streaming: server sends ~5 minutes, then waits for "more" message

func DisableCamera added in v0.3.2

func DisableCamera(app *Application, verboseLevel VerboseLevel) func(ctx *gin.Context)

DisableCamera turns off stream for specific stream ID

func EnableCamera added in v0.3.2

func EnableCamera(app *Application, verboseLevel VerboseLevel) func(ctx *gin.Context)

EnableCamera adds new stream if does not exist

func HLSWrapper added in v0.2.0

func HLSWrapper(hlsConf *HLSInfo, verboseLevel VerboseLevel) func(ctx *gin.Context)

HLSWrapper returns HLS handler (static files)

func ListWrapper added in v0.2.0

func ListWrapper(app *Application, verboseLevel VerboseLevel) func(ctx *gin.Context)

ListWrapper returns list of streams

func MutexLocked added in v0.5.0

func MutexLocked(m *sync.Mutex) bool

func RWMutexLocked added in v0.5.0

func RWMutexLocked(rw *sync.RWMutex) bool

func RWMutexRLocked added in v0.5.0

func RWMutexRLocked(rw *sync.RWMutex) bool

func StatusWrapper added in v0.2.0

func StatusWrapper(app *Application, verboseLevel VerboseLevel) func(ctx *gin.Context)

StatusWrapper returns statuses for list of streams

func UploadToMinio added in v0.6.0

func UploadToMinio(minioStorage storage.ArchiveStorage, segmentName, bucket, sourceFileName string) (string, error)

func WebSocketWrapper added in v0.2.0

func WebSocketWrapper(streamsStorage *StreamsStorage, wsUpgrader *websocket.Upgrader, verboseLevel VerboseLevel) func(ctx *gin.Context)

WebSocketWrapper returns WS handler

Types

type APIConfiguration added in v0.4.0

type APIConfiguration struct {
	Enabled bool         `json:"-"`
	Host    string       `json:"host"`
	Port    int32        `json:"port"`
	Mode    string       `json:"-"`
	Verbose VerboseLevel `json:"-"`
}

APIConfiguration is just copy of configuration.APIConfiguration but with some not exported fields

type Application added in v0.2.0

type Application struct {
	APICfg         APIConfiguration   `json:"api"`
	VideoServerCfg VideoConfiguration `json:"video"`
	Streams        StreamsStorage     `json:"streams"`
	HLS            HLSInfo            `json:"hls"`
	// Global archive config for playback fallback
	ArchiveConfig ArchiveInfo  `json:"-"`
	CorsConfig    *cors.Config `json:"-"`
	// contains filtered or unexported fields
}

Application is a configuration parameters for application

func NewApplication added in v0.2.0

func NewApplication(cfg *configuration.Configuration) (*Application, error)

NewApplication Prepare configuration for application

func (*Application) GetArchiveStorageForPlayback added in v0.8.0

func (app *Application) GetArchiveStorageForPlayback(streamID uuid.UUID) (*StreamArchiveWrapper, error)

GetArchiveStorageForPlayback returns archive storage for playback. First tries stream-specific archive, then falls back to global archive config.

func (*Application) RunStream added in v0.5.0

func (app *Application) RunStream(ctx context.Context, streamID uuid.UUID) error

func (*Application) StartAPIServer added in v0.3.2

func (app *Application) StartAPIServer() error

StartAPIServer starts server with API functionality

func (*Application) StartStream added in v0.3.2

func (app *Application) StartStream(streamID uuid.UUID)

StartStream starts single video stream

func (*Application) StartStreams added in v0.2.0

func (app *Application) StartStreams()

StartStreams starts all video streams

func (*Application) StartVideoServer added in v0.3.2

func (app *Application) StartVideoServer()

StartVideoServer initializes "video" server and run it (MSE-websockets and HLS-static files)

type ArchiveInfo added in v0.8.0

type ArchiveInfo struct {
	Recording    bool
	Serving      bool
	Directory    string
	MsPerSegment int64
}

ArchiveInfo stores global archive configuration

type ArchiveRangesResponse added in v0.8.0

type ArchiveRangesResponse struct {
	StreamID string      `json:"stream_id"`
	Ranges   []TimeRange `json:"ranges"`
}

ArchiveRangesResponse is the API response for available time ranges

type EnablePostData added in v0.3.2

type EnablePostData struct {
	GUID        uuid.UUID `json:"guid"`
	URL         string    `json:"url"`
	OutputTypes []string  `json:"output_types"`
}

EnablePostData is a POST-body for API which enables to turn on/off specific streams

type HLSInfo added in v0.4.0

type HLSInfo struct {
	MsPerSegment int64  `json:"hls_ms_per_segment"`
	Directory    string `json:"-"`
	WindowSize   uint   `json:"hls_window_size"`
	Capacity     uint   `json:"hls_window_capacity"`
}

HLSInfo is an information about HLS parameters for server

type MP4Demuxer added in v0.7.0

type MP4Demuxer struct {
	// contains filtered or unexported fields
}

MP4Demuxer wraps mp4ff to provide av.Packet compatible output

func NewMP4Demuxer added in v0.7.0

func NewMP4Demuxer(filePath string) (*MP4Demuxer, error)

NewMP4Demuxer creates a new demuxer for the given file

func (*MP4Demuxer) Close added in v0.7.0

func (d *MP4Demuxer) Close() error

Close closes the demuxer and underlying file

func (*MP4Demuxer) ReadPacket added in v0.7.0

func (d *MP4Demuxer) ReadPacket() (av.Packet, error)

ReadPacket reads the next packet from the demuxer

func (*MP4Demuxer) SeekToStart added in v0.7.0

func (d *MP4Demuxer) SeekToStart()

SeekToStart resets all tracks to the beginning

func (*MP4Demuxer) Streams added in v0.7.0

func (d *MP4Demuxer) Streams() []av.CodecData

Streams returns the codec data for all tracks

type MP4ffMuxer added in v0.7.0

type MP4ffMuxer struct {
	// contains filtered or unexported fields
}

MP4ffMuxer wraps mp4ff library to create MP4 files

func NewMP4ffMuxer added in v0.7.0

func NewMP4ffMuxer(w io.WriteSeeker) *MP4ffMuxer

NewMP4ffMuxer creates a new muxer using mp4ff library

func (*MP4ffMuxer) Duration added in v0.7.0

func (m *MP4ffMuxer) Duration() time.Duration

Duration returns the duration of the longest track (relative, not absolute)

func (*MP4ffMuxer) WriteHeader added in v0.7.0

func (m *MP4ffMuxer) WriteHeader(codecs []av.CodecData) error

WriteHeader initializes the muxer with codec information

func (*MP4ffMuxer) WritePacket added in v0.7.0

func (m *MP4ffMuxer) WritePacket(pkt av.Packet) error

WritePacket writes a packet to the muxer

func (*MP4ffMuxer) WriteTrailer added in v0.7.0

func (m *MP4ffMuxer) WriteTrailer() error

WriteTrailer finalizes the file with moov box

type ServerInfo added in v0.2.0

type ServerInfo struct {
	HTTPAddr      string `json:"http_addr"`
	VideoHTTPPort int32  `json:"http_port"`
	APIHTTPPort   int32  `json:"-"`
}

ServerInfo is an information about server

type StopSignal added in v0.6.0

type StopSignal uint8

type StreamArchiveWrapper added in v0.6.0

type StreamArchiveWrapper struct {
	// contains filtered or unexported fields
}

type StreamConfiguration

type StreamConfiguration struct {
	URL                  string               `json:"url"`
	Status               bool                 `json:"status"`
	SupportedOutputTypes []StreamType         `json:"supported_output_types"`
	Codecs               []av.CodecData       `json:"codecs"`
	Clients              map[uuid.UUID]viewer `json:"-"`
	// contains filtered or unexported fields
}

StreamConfiguration is a configuration parameters for specific stream

func NewStreamConfiguration added in v0.4.0

func NewStreamConfiguration(streamURL string, supportedTypes []StreamType) *StreamConfiguration

NewStreamConfiguration returns default configuration

type StreamInfoShorten added in v0.4.0

type StreamInfoShorten struct {
	StreamID string `json:"stream_id"`
}

type StreamType added in v0.5.0

type StreamType uint16

func (StreamType) String added in v0.5.0

func (iotaIdx StreamType) String() string

type StreamsInfoShortenList added in v0.4.0

type StreamsInfoShortenList struct {
	Data []StreamInfoShorten `json:"data"`
}

type StreamsStorage added in v0.4.0

type StreamsStorage struct {
	sync.RWMutex
	// contains filtered or unexported fields
}

StreamsStorage Map wrapper for map[uuid.UUID]*StreamConfiguration with mutex for concurrent usage

func NewStreamsStorageDefault added in v0.4.0

func NewStreamsStorageDefault() StreamsStorage

NewStreamsStorageDefault prepares new allocated storage

func (*StreamsStorage) AddCodecForStream added in v0.5.2

func (streams *StreamsStorage) AddCodecForStream(streamID uuid.UUID, codecs []av.CodecData) error

AddCodecForStream appends new codecs data for the given stream

func (*StreamsStorage) AddViewer added in v0.5.2

func (streams *StreamsStorage) AddViewer(streamID uuid.UUID) (uuid.UUID, chan av.Packet, error)

AddViewer adds client to the given stream. Return newly client ID, buffered channel for stream on success

func (*StreamsStorage) CastPacket added in v0.5.2

func (streams *StreamsStorage) CastPacket(streamID uuid.UUID, pck av.Packet, hlsEnabled, archiveEnabled bool) error

CastPacket cast AV Packet to viewers and possible to HLS/MP4 channels

func (*StreamsStorage) DeleteViewer added in v0.5.2

func (streams *StreamsStorage) DeleteViewer(streamID, clientID uuid.UUID)

DeleteViewer removes given client from the stream

func (*StreamsStorage) GetAllStreamsIDS added in v0.5.2

func (streams *StreamsStorage) GetAllStreamsIDS() []uuid.UUID

GetAllStreamsIDS returns all storage streams' keys as slice

func (*StreamsStorage) GetCodecsDataForStream added in v0.5.2

func (streams *StreamsStorage) GetCodecsDataForStream(streamID uuid.UUID) ([]av.CodecData, error)

GetCodecsDataForStream returns COPY of codecs data for the given stream

func (*StreamsStorage) GetStreamArchiveStorage added in v0.5.2

func (streams *StreamsStorage) GetStreamArchiveStorage(streamID uuid.UUID) *StreamArchiveWrapper

GetStreamArchiveStorage returns pointer to the archive storage for the given stream

func (*StreamsStorage) GetStreamInfo added in v0.5.2

func (streams *StreamsStorage) GetStreamInfo(streamID uuid.UUID) (string, []StreamType)

GetStreamInfo returns stream URL and its supported output types

func (*StreamsStorage) GetVerboseLevelForStream added in v0.5.2

func (streams *StreamsStorage) GetVerboseLevelForStream(streamID uuid.UUID) VerboseLevel

GetVerboseLevelForStream returst verbose level for the given stream

func (*StreamsStorage) IsArchiveEnabledForStream added in v0.5.2

func (streams *StreamsStorage) IsArchiveEnabledForStream(streamID uuid.UUID) (bool, error)

IsArchiveEnabledForStream returns whenever archive has been enabled for stream

func (*StreamsStorage) StreamExists added in v0.5.2

func (streams *StreamsStorage) StreamExists(streamID uuid.UUID) bool

StreamExists checks whenever given stream ID exists in storage

func (*StreamsStorage) TypeExistsForStream added in v0.5.2

func (streams *StreamsStorage) TypeExistsForStream(streamID uuid.UUID, streamType StreamType) bool

TypeExistsForStream checks whenever specific stream ID supports then given output stream type

func (*StreamsStorage) UpdateArchiveStorageForStream added in v0.5.2

func (streams *StreamsStorage) UpdateArchiveStorageForStream(streamID uuid.UUID, archiveStorage *StreamArchiveWrapper) error

UpdateArchiveStorageForStream updates archive storage configuration (it override existing one!)

func (*StreamsStorage) UpdateStreamStatus added in v0.5.2

func (streams *StreamsStorage) UpdateStreamStatus(streamID uuid.UUID, status bool) error

UpdateStreamStatus sets new status value for the given stream

type TimeRange added in v0.8.0

type TimeRange struct {
	Start time.Time `json:"start"`
	End   time.Time `json:"end"`
}

TimeRange represents a continuous time range of available archive

type VerboseLevel added in v0.5.0

type VerboseLevel uint16

func NewVerboseLevelFrom added in v0.5.0

func NewVerboseLevelFrom(str string) VerboseLevel

type VideoConfiguration added in v0.4.0

type VideoConfiguration struct {
	Host    string       `json:"host"`
	Port    int32        `json:"port"`
	Mode    string       `json:"-"`
	Verbose VerboseLevel `json:"-"`
}

VideoConfiguration is just copy of configuration.VideoConfiguration but with some not exported fields

Directories

Path Synopsis
cmd
video_server command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL