Simple RTMP Server
The SimpleRtmpServer sample demonstrates how to use RtmpServer in stand-alone mode with manual media flow handling. Unlike the Multi-Protocol Server where StreamingServer manages media routing automatically, this sample handles publisher/client connections, stream registration, and media sample forwarding directly in user code.
This approach is useful when you need full control over the media pipeline — for example, custom routing logic, per-client filtering, or integration with external processing systems.
Overview
The SimpleRtmpServer class performs the following:
- Configures RTMP global settings and server parameters
- Creates an RtmpServer and subscribes to connection events
- Tracks publishers and clients manually using dictionaries
- Forwards media samples from publishers to all connected clients on the same stream
- Optionally records published streams to file
Initialization
RTMP Global Settings
VAST.RTMP.RtmpGlobal.RtmpDefaultAckWindowSize = 2500000;
VAST.RTMP.RtmpGlobal.RtmpDefaultOurChunkSize = 1024;
VAST.RTMP.RtmpGlobal.RtmpSessionInactivityTimeoutMs = 30000;
VAST.RTMP.RtmpGlobal.RtmpApplication = "live";
| Setting | Value | Description |
|---|---|---|
RtmpDefaultAckWindowSize |
2500000 | Acknowledgement window size in bytes |
RtmpDefaultOurChunkSize |
1024 | Chunk size for outgoing RTMP messages |
RtmpSessionInactivityTimeoutMs |
30000 | Disconnect inactive sessions after 30 seconds |
RtmpApplication |
"live" |
Default RTMP application name |
Server Parameters
var pars = new VAST.RTMP.RtmpServerParameters();
pars.RtmpEndPoints.Add(new IPEndPoint(IPAddress.Any, 1935));
pars.RtmpApplication = "live";
For RTMPS (secure), uncomment the RTMPS endpoint and certificate:
pars.RtmpsEndPoints.Add(new IPEndPoint(IPAddress.Any, 1936));
pars.CertificateThumbprint = "YOUR_CERTIFICATE_THUMBPRINT";
Creating the Server
this.server = new VAST.RTMP.RtmpServer(pars);
this.server.Connected += Server_Connected;
this.server.Disconnected += Server_Disconnected;
this.server.PublisherConnected += Server_PublisherConnected;
this.server.ClientConnected += Server_ClientConnected;
RtmpServer exposes four events for connection lifecycle management.
Internal State
The sample tracks connections using four dictionaries:
Dictionary<string, PublishedStream> publishedStreams;
Dictionary<EndPoint, PublishedStream> connectedPublishers;
Dictionary<EndPoint, PublishedStream> connectedClients;
Dictionary<Guid, PublishedStream> activeWriters;
Each PublishedStream holds the stream name, media type descriptors, and a dictionary of connected client sinks:
class PublishedStream
{
public string PublishName { get; set; }
public List<VAST.Common.MediaType> MediaStreams { get; set; }
public Dictionary<EndPoint, VAST.Media.IMediaSink> ConnectedClients { get; set; }
}
Event Handlers
Connected
private void Server_Connected(object sender, VAST.Transport.TransportArgs e)
{
// new peer connected, it's unknown yet whether it's a client or publisher
}
Fired when a new TCP connection is established. At this point the peer's role (publisher or client) is not yet determined.
PublisherConnected
private void Server_PublisherConnected(object sender, VAST.Network.INetworkSource publisher)
{
publisher.Accept = true;
publisher.NewStream += Publisher_NewStream;
publisher.NewSample += Publisher_NewSample;
PublishedStream publishedStream = new PublishedStream(publisher.PublishingPath);
this.publishedStreams.Add(publisher.PublishingPath, publishedStream);
this.connectedPublishers.Add(publisher.EndPoint, publishedStream);
}
When a publisher connects, the sample:
- Sets
Accept = trueto allow the connection - Subscribes to
NewStream(media type announcements) andNewSample(media data) events on the publisher - Creates a
PublishedStreamrecord and registers it by stream name and endpoint
ClientConnected
private void Server_ClientConnected(object sender, VAST.Network.INetworkSink client)
{
if (!this.publishedStreams.ContainsKey(client.PublishingPath))
{
return; // stream not published
}
PublishedStream publishedStream = this.publishedStreams[client.PublishingPath];
client.Accept = true;
int index = 0;
foreach (VAST.Common.MediaType mt in publishedStream.MediaStreams)
{
client.AddStream(index++, mt);
}
publishedStream.ConnectedClients.Add(client.EndPoint, client);
this.connectedClients.Add(client.EndPoint, publishedStream);
}
When a client connects, the sample:
- Verifies the requested stream is currently published
- Sets
Accept = trueto allow the connection - Calls
AddStreamfor each media stream to inform the client of the available tracks - Adds the client to the published stream's connected clients dictionary
Disconnected
private void Server_Disconnected(object sender, VAST.Transport.TransportArgs e)
Handles both publisher and client disconnections:
- Publisher disconnect — stops all connected clients on that stream, removes the published stream
- Client disconnect — removes the client from the published stream's connected clients
Media Flow
NewStream Event
private void Publisher_NewStream(object sender, Media.NewStreamEventArgs e)
{
PublishedStream publishedStream = this.publishedStreams[publisher.PublishingPath];
publishedStream.MediaStreams[e.StreamIndex] = e.MediaType.Clone();
}
Fired when the publisher announces a new media stream (video, audio, etc.). The media type is saved — it will be sent to clients that connect later via AddStream.
NewSample Event
private void Publisher_NewSample(object sender, Media.NewSampleEventArgs e)
{
PublishedStream publishedStream = this.publishedStreams[publisher.PublishingPath];
foreach (VAST.Media.IMediaSink client in publishedStream.ConnectedClients.Values)
{
client.PushMedia(e.Sample.StreamIndex, e.Sample);
}
}
Each media sample from the publisher is forwarded to all connected clients via PushMedia. This is the core media routing logic — in a production application, this is where custom filtering, transformation, or selective forwarding could be implemented.
File Recording
The sample includes an optional recording feature that writes published streams to file:
private void startRecording(PublishedStream publishedStream, string path)
{
VAST.Media.IMediaSink fileWriter = VAST.Media.SinkFactory.Create(path);
fileWriter.Uri = path;
fileWriter.Open();
int streamIndex = 0;
foreach (VAST.Common.MediaType mt in publishedStream.MediaStreams)
{
fileWriter.AddStream(streamIndex++, mt);
}
fileWriter.Start();
publishedStream.ConnectedClients.Add(new IPEndPoint(0, 0), fileWriter);
}
The file writer is created via SinkFactory and added to the connected clients dictionary with a dummy endpoint. Since it implements IMediaSink, it receives the same PushMedia calls as regular clients — no special handling is needed.
For MP4 recording, additional parameters can be configured:
if (fileWriter is VAST.File.ISO.IsoSink)
{
((VAST.File.ISO.IsoSink)fileWriter).ParsingParameters =
new VAST.File.ISO.ParsingParameters { WriteMediaDataLast = true };
}
Access URLs
| Protocol | URL |
|---|---|
| RTMP Publish | rtmp://server/live/{stream-name} |
| RTMP Play | rtmp://server/live/{stream-name} |
| RTMPS Publish | rtmps://server:1936/live/{stream-name} |
| RTMPS Play | rtmps://server:1936/live/{stream-name} |
See Also
- Sample Applications — overview of all demo projects
- .NET Server Demo — parent page with setup instructions, license key configuration, and access URL reference
- Multi-Protocol Server — full-featured multi-protocol server with automatic media flow
- VAST.RTMP Library — RTMP API reference