Table of Contents

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:

  1. Configures RTMP global settings and server parameters
  2. Creates an RtmpServer and subscribes to connection events
  3. Tracks publishers and clients manually using dictionaries
  4. Forwards media samples from publishers to all connected clients on the same stream
  5. 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:

  1. Sets Accept = true to allow the connection
  2. Subscribes to NewStream (media type announcements) and NewSample (media data) events on the publisher
  3. Creates a PublishedStream record 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:

  1. Verifies the requested stream is currently published
  2. Sets Accept = true to allow the connection
  3. Calls AddStream for each media stream to inform the client of the available tracks
  4. 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