> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/bluenviron/gortsplib/llms.txt
> Use this file to discover all available pages before exploring further.

# Handling requests

> Implement ServerHandler callbacks to control how your server responds.

The server dispatches incoming RTSP messages to your handler struct by checking which `ServerHandler*` interfaces it implements. Each interface defines a single method. Implement only the interfaces relevant to your use case.

## Handler interfaces

### Connection lifecycle

<AccordionGroup>
  <Accordion title="ServerHandlerOnConnOpen">
    Called when a new TCP connection is accepted by the server.

    ```go theme={null}
    func (h *myHandler) OnConnOpen(ctx *gortsplib.ServerHandlerOnConnOpenCtx) {
        // ctx.Conn — the new *ServerConn
        log.Printf("conn opened")
    }
    ```
  </Accordion>

  <Accordion title="ServerHandlerOnConnClose">
    Called when a connection is closed, whether by the client, the server, or an error.

    ```go theme={null}
    func (h *myHandler) OnConnClose(ctx *gortsplib.ServerHandlerOnConnCloseCtx) {
        // ctx.Conn  — the closed *ServerConn
        // ctx.Error — the reason for closure
        log.Printf("conn closed: %v", ctx.Error)
    }
    ```
  </Accordion>
</AccordionGroup>

### Session lifecycle

<AccordionGroup>
  <Accordion title="ServerHandlerOnSessionOpen">
    Called when a new RTSP session is created. Sessions are created lazily on the first SETUP or ANNOUNCE request.

    ```go theme={null}
    func (h *myHandler) OnSessionOpen(ctx *gortsplib.ServerHandlerOnSessionOpenCtx) {
        // ctx.Session — the new *ServerSession
        // ctx.Conn    — the connection that created the session
        log.Printf("session opened")
    }
    ```
  </Accordion>

  <Accordion title="ServerHandlerOnSessionClose">
    Called when a session is closed. Use this to tear down any resources associated with the session — for example, closing a `ServerStream` when the publisher disconnects.

    ```go theme={null}
    func (h *myHandler) OnSessionClose(ctx *gortsplib.ServerHandlerOnSessionCloseCtx) {
        // ctx.Session — the closed *ServerSession
        // ctx.Error   — the reason for closure
        if h.stream != nil && ctx.Session == h.publisher {
            h.stream.Close()
            h.stream = nil
        }
    }
    ```
  </Accordion>
</AccordionGroup>

### Request/response logging

<AccordionGroup>
  <Accordion title="ServerHandlerOnRequest">
    Called for every raw RTSP request received on a connection, before the server processes it. Useful for logging or inspection.

    ```go theme={null}
    func (h *myHandler) OnRequest(conn *gortsplib.ServerConn, req *base.Request) {
        log.Printf("request: %v", req.Method)
    }
    ```
  </Accordion>

  <Accordion title="ServerHandlerOnResponse">
    Called for every RTSP response sent by the server. Useful for logging or inspection.

    ```go theme={null}
    func (h *myHandler) OnResponse(conn *gortsplib.ServerConn, res *base.Response) {
        log.Printf("response: %v", res.StatusCode)
    }
    ```
  </Accordion>
</AccordionGroup>

### RTSP methods

<AccordionGroup>
  <Accordion title="ServerHandlerOnDescribe">
    Called when a client sends a `DESCRIBE` request. Readers call this to fetch the stream description (SDP) before calling `SETUP`.

    Return the `*ServerStream` the client should be offered, or `nil` with `StatusNotFound` if no stream is available.

    ```go theme={null}
    func (h *myHandler) OnDescribe(
        ctx *gortsplib.ServerHandlerOnDescribeCtx,
    ) (*base.Response, *gortsplib.ServerStream, error) {
        // ctx.Conn    — connection sending the request
        // ctx.Request — raw RTSP request
        // ctx.Path    — requested path
        // ctx.Query   — query string
        if h.stream == nil {
            return &base.Response{StatusCode: base.StatusNotFound}, nil, nil
        }
        return &base.Response{StatusCode: base.StatusOK}, h.stream, nil
    }
    ```
  </Accordion>

  <Accordion title="ServerHandlerOnAnnounce">
    Called when a publisher sends an `ANNOUNCE` request. This is where you create a `ServerStream` for the incoming publication.

    ```go theme={null}
    func (h *myHandler) OnAnnounce(
        ctx *gortsplib.ServerHandlerOnAnnounceCtx,
    ) (*base.Response, error) {
        // ctx.Session     — the publisher's *ServerSession
        // ctx.Description — the SDP description sent by the publisher
        // ctx.Path        — announced path
        h.stream = &gortsplib.ServerStream{
            Server: h.server,
            Desc:   ctx.Description,
        }
        if err := h.stream.Initialize(); err != nil {
            return &base.Response{StatusCode: base.StatusInternalServerError}, err
        }
        h.publisher = ctx.Session
        return &base.Response{StatusCode: base.StatusOK}, nil
    }
    ```
  </Accordion>

  <Accordion title="ServerHandlerOnSetup">
    Called for every `SETUP` request. Both readers and publishers send `SETUP`. Use `ctx.Session.State()` to distinguish them:

    * `ServerSessionStatePreRecord` — the session is setting up a track to publish.
    * `ServerSessionStateInitial` or `ServerSessionStatePrePlay` — the session is setting up a track to read.

    Return the `*ServerStream` for readers. For publishers, return `nil`.

    ```go theme={null}
    func (h *myHandler) OnSetup(
        ctx *gortsplib.ServerHandlerOnSetupCtx,
    ) (*base.Response, *gortsplib.ServerStream, error) {
        // publisher setup — no stream needed
        if ctx.Session.State() == gortsplib.ServerSessionStatePreRecord {
            return &base.Response{StatusCode: base.StatusOK}, nil, nil
        }

        // reader setup — return the stream to subscribe the session
        if h.stream == nil {
            return &base.Response{StatusCode: base.StatusNotFound}, nil, nil
        }
        return &base.Response{StatusCode: base.StatusOK}, h.stream, nil
    }
    ```
  </Accordion>

  <Accordion title="ServerHandlerOnPlay">
    Called when a reader sends a `PLAY` request, after all tracks are set up. The server begins delivering packets to the reader.

    ```go theme={null}
    func (h *myHandler) OnPlay(
        ctx *gortsplib.ServerHandlerOnPlayCtx,
    ) (*base.Response, error) {
        return &base.Response{StatusCode: base.StatusOK}, nil
    }
    ```
  </Accordion>

  <Accordion title="ServerHandlerOnRecord">
    Called when a publisher sends a `RECORD` request. Register packet callbacks here using `ctx.Session.OnPacketRTPAny` or `ctx.Session.OnPacketRTP`.

    ```go theme={null}
    func (h *myHandler) OnRecord(
        ctx *gortsplib.ServerHandlerOnRecordCtx,
    ) (*base.Response, error) {
        ctx.Session.OnPacketRTPAny(func(medi *description.Media, _ format.Format, pkt *rtp.Packet) {
            h.stream.WritePacketRTP(medi, pkt) //nolint:errcheck
        })
        return &base.Response{StatusCode: base.StatusOK}, nil
    }
    ```
  </Accordion>

  <Accordion title="ServerHandlerOnPause">
    Called when a reader or publisher sends a `PAUSE` request. Packet delivery is suspended until the next `PLAY` or `RECORD`.

    ```go theme={null}
    func (h *myHandler) OnPause(
        ctx *gortsplib.ServerHandlerOnPauseCtx,
    ) (*base.Response, error) {
        return &base.Response{StatusCode: base.StatusOK}, nil
    }
    ```
  </Accordion>

  <Accordion title="ServerHandlerOnGetParameter / ServerHandlerOnSetParameter">
    Called for `GET_PARAMETER` and `SET_PARAMETER` requests. These are often used by clients as keepalives.

    ```go theme={null}
    func (h *myHandler) OnGetParameter(
        ctx *gortsplib.ServerHandlerOnGetParameterCtx,
    ) (*base.Response, error) {
        return &base.Response{StatusCode: base.StatusOK}, nil
    }

    func (h *myHandler) OnSetParameter(
        ctx *gortsplib.ServerHandlerOnSetParameterCtx,
    ) (*base.Response, error) {
        return &base.Response{StatusCode: base.StatusOK}, nil
    }
    ```
  </Accordion>
</AccordionGroup>

### Error and diagnostic callbacks

<AccordionGroup>
  <Accordion title="ServerHandlerOnPacketsLost">
    Called when the server detects lost RTP packets from a publisher, based on RTCP receiver reports.

    ```go theme={null}
    func (h *myHandler) OnPacketsLost(ctx *gortsplib.ServerHandlerOnPacketsLostCtx) {
        // ctx.Session — the session with losses
        // ctx.Lost    — cumulative count of lost packets
        log.Printf("packets lost: %d", ctx.Lost)
    }
    ```
  </Accordion>

  <Accordion title="ServerHandlerOnDecodeError">
    Called when a non-fatal decode error occurs while processing incoming RTP packets. The session continues.

    ```go theme={null}
    func (h *myHandler) OnDecodeError(ctx *gortsplib.ServerHandlerOnDecodeErrorCtx) {
        log.Printf("decode error: %v", ctx.Error)
    }
    ```
  </Accordion>

  <Accordion title="ServerHandlerOnStreamWriteError">
    Called when a `ServerStream` fails to deliver a packet to a particular reader session. The reader is disconnected automatically; this callback is informational.

    ```go theme={null}
    func (h *myHandler) OnStreamWriteError(ctx *gortsplib.ServerHandlerOnStreamWriteErrorCtx) {
        log.Printf("write error for session: %v", ctx.Error)
    }
    ```
  </Accordion>
</AccordionGroup>

## ServerStream

`ServerStream` holds the stream description and distributes packets to all readers. You create one in `OnAnnounce` (for live relay) or before starting the server (for file serving).

```go theme={null}
// Create and initialize a stream.
stream := &gortsplib.ServerStream{
    Server: s,       // pointer to the running *Server
    Desc:   sdpDesc, // *description.Session — the SDP
}
if err := stream.Initialize(); err != nil {
    return err
}
defer stream.Close()

// Write a packet to all active readers.
stream.WritePacketRTP(media, rtpPacket)

// Write a packet with an explicit NTP timestamp.
stream.WritePacketRTPWithNTP(media, rtpPacket, time.Now())

// Write a raw RTCP packet.
stream.WritePacketRTCP(media, rtcpPacket)
```

<Note>
  Call `stream.Close()` when the publisher disconnects. This immediately disconnects all active readers.
</Note>

## Session states

`ServerSession.State()` returns the current state of a session. The most common use is distinguishing a publisher from a reader inside `OnSetup`.

| State | Meaning |
| - | - |
| `ServerSessionStateInitial` | Session exists but no tracks are set up yet. |
| `ServerSessionStatePrePlay` | One or more tracks set up, waiting for PLAY. |
| `ServerSessionStatePlay` | Actively receiving packets as a reader. |
| `ServerSessionStatePreRecord` | One or more tracks set up, waiting for RECORD. |
| `ServerSessionStateRecord` | Actively sending packets as a publisher. |

## Full example: relay publisher to readers

This example accepts a single publisher and forwards all incoming RTP packets to every connected reader.

```go server/main.go theme={null}
// Package main contains an example.
package main

import (
	"log"
	"sync"

	"github.com/pion/rtp"

	"github.com/bluenviron/gortsplib/v5"
	"github.com/bluenviron/gortsplib/v5/pkg/base"
	"github.com/bluenviron/gortsplib/v5/pkg/description"
	"github.com/bluenviron/gortsplib/v5/pkg/format"
)

type serverHandler struct {
	server    *gortsplib.Server
	mutex     sync.RWMutex
	stream    *gortsplib.ServerStream
	publisher *gortsplib.ServerSession
}

func (sh *serverHandler) OnConnOpen(_ *gortsplib.ServerHandlerOnConnOpenCtx) {
	log.Printf("conn opened")
}

func (sh *serverHandler) OnConnClose(ctx *gortsplib.ServerHandlerOnConnCloseCtx) {
	log.Printf("conn closed (%v)", ctx.Error)
}

func (sh *serverHandler) OnSessionOpen(_ *gortsplib.ServerHandlerOnSessionOpenCtx) {
	log.Printf("session opened")
}

func (sh *serverHandler) OnSessionClose(ctx *gortsplib.ServerHandlerOnSessionCloseCtx) {
	log.Printf("session closed")

	sh.mutex.Lock()
	defer sh.mutex.Unlock()

	// if the session is the publisher,
	// close the stream and disconnect any reader.
	if sh.stream != nil && ctx.Session == sh.publisher {
		sh.stream.Close()
		sh.stream = nil
	}
}

// called when receiving a DESCRIBE request.
func (sh *serverHandler) OnDescribe(
	_ *gortsplib.ServerHandlerOnDescribeCtx,
) (*base.Response, *gortsplib.ServerStream, error) {
	log.Printf("DESCRIBE request")

	sh.mutex.RLock()
	defer sh.mutex.RUnlock()

	// no one is publishing yet
	if sh.stream == nil {
		return &base.Response{
			StatusCode: base.StatusNotFound,
		}, nil, nil
	}

	// send medias that are being published to the client
	return &base.Response{
		StatusCode: base.StatusOK,
	}, sh.stream, nil
}

// called when receiving an ANNOUNCE request.
func (sh *serverHandler) OnAnnounce(ctx *gortsplib.ServerHandlerOnAnnounceCtx) (*base.Response, error) {
	log.Printf("ANNOUNCE request")

	sh.mutex.Lock()
	defer sh.mutex.Unlock()

	// disconnect existing publisher
	if sh.stream != nil {
		sh.stream.Close()
		sh.publisher.Close()
	}

	// create the stream and save the publisher
	sh.stream = &gortsplib.ServerStream{
		Server: sh.server,
		Desc:   ctx.Description,
	}
	err := sh.stream.Initialize()
	if err != nil {
		panic(err)
	}
	sh.publisher = ctx.Session

	return &base.Response{
		StatusCode: base.StatusOK,
	}, nil
}

// called when receiving a SETUP request.
func (sh *serverHandler) OnSetup(ctx *gortsplib.ServerHandlerOnSetupCtx) (
	*base.Response, *gortsplib.ServerStream, error,
) {
	log.Printf("SETUP request")

	// SETUP is used by both readers and publishers. In case of publishers, just return StatusOK.
	if ctx.Session.State() == gortsplib.ServerSessionStatePreRecord {
		return &base.Response{
			StatusCode: base.StatusOK,
		}, nil, nil
	}

	sh.mutex.RLock()
	defer sh.mutex.RUnlock()

	// no one is publishing yet
	if sh.stream == nil {
		return &base.Response{
			StatusCode: base.StatusNotFound,
		}, nil, nil
	}

	return &base.Response{
		StatusCode: base.StatusOK,
	}, sh.stream, nil
}

// called when receiving a PLAY request.
func (sh *serverHandler) OnPlay(_ *gortsplib.ServerHandlerOnPlayCtx) (*base.Response, error) {
	log.Printf("PLAY request")

	return &base.Response{
		StatusCode: base.StatusOK,
	}, nil
}

// called when receiving a RECORD request.
func (sh *serverHandler) OnRecord(ctx *gortsplib.ServerHandlerOnRecordCtx) (*base.Response, error) {
	log.Printf("RECORD request")

	// called when receiving a RTP packet
	ctx.Session.OnPacketRTPAny(func(medi *description.Media, _ format.Format, pkt *rtp.Packet) {
		// route the RTP packet to all readers
		err := sh.stream.WritePacketRTP(medi, pkt)
		if err != nil {
			log.Printf("ERR: %v", err)
		}
	})

	return &base.Response{
		StatusCode: base.StatusOK,
	}, nil
}

func main() {
	// configure the server
	h := &serverHandler{}
	h.server = &gortsplib.Server{
		Handler:           h,
		RTSPAddress:       ":8554",
		UDPRTPAddress:     ":8000",
		UDPRTCPAddress:    ":8001",
		MulticastIPRange:  "224.1.0.0/16",
		MulticastRTPPort:  8002,
		MulticastRTCPPort: 8003,
	}

	// start server and wait until a fatal error
	log.Printf("server is ready on %s", h.server.RTSPAddress)
	panic(h.server.StartAndWait())
}
```

## Example: serve H264 from disk

This example creates a `ServerStream` before accepting connections and streams a MPEG-TS file to all readers.

```go server-play-format-h264-from-disk/main.go theme={null}
// Package main contains an example.
package main

import (
	"log"
	"sync"

	"github.com/bluenviron/gortsplib/v5"
	"github.com/bluenviron/gortsplib/v5/pkg/base"
	"github.com/bluenviron/gortsplib/v5/pkg/description"
	"github.com/bluenviron/gortsplib/v5/pkg/format"
)

type serverHandler struct {
	server *gortsplib.Server
	stream *gortsplib.ServerStream
	mutex  sync.RWMutex
}

func (sh *serverHandler) OnConnOpen(_ *gortsplib.ServerHandlerOnConnOpenCtx) {
	log.Printf("conn opened")
}

func (sh *serverHandler) OnConnClose(ctx *gortsplib.ServerHandlerOnConnCloseCtx) {
	log.Printf("conn closed (%v)", ctx.Error)
}

func (sh *serverHandler) OnSessionOpen(_ *gortsplib.ServerHandlerOnSessionOpenCtx) {
	log.Printf("session opened")
}

func (sh *serverHandler) OnSessionClose(_ *gortsplib.ServerHandlerOnSessionCloseCtx) {
	log.Printf("session closed")
}

func (sh *serverHandler) OnDescribe(
	_ *gortsplib.ServerHandlerOnDescribeCtx,
) (*base.Response, *gortsplib.ServerStream, error) {
	log.Printf("DESCRIBE request")

	sh.mutex.RLock()
	defer sh.mutex.RUnlock()

	return &base.Response{
		StatusCode: base.StatusOK,
	}, sh.stream, nil
}

func (sh *serverHandler) OnSetup(
	_ *gortsplib.ServerHandlerOnSetupCtx,
) (*base.Response, *gortsplib.ServerStream, error) {
	log.Printf("SETUP request")

	sh.mutex.RLock()
	defer sh.mutex.RUnlock()

	return &base.Response{
		StatusCode: base.StatusOK,
	}, sh.stream, nil
}

func (sh *serverHandler) OnPlay(_ *gortsplib.ServerHandlerOnPlayCtx) (*base.Response, error) {
	log.Printf("PLAY request")

	return &base.Response{
		StatusCode: base.StatusOK,
	}, nil
}

func main() {
	h := &serverHandler{}

	// prevent clients from connecting to the server until the stream is properly set up
	h.mutex.Lock()

	// create the server
	h.server = &gortsplib.Server{
		Handler:           h,
		RTSPAddress:       ":8554",
		UDPRTPAddress:     ":8000",
		UDPRTCPAddress:    ":8001",
		MulticastIPRange:  "224.1.0.0/16",
		MulticastRTPPort:  8002,
		MulticastRTCPPort: 8003,
	}

	// start the server
	err := h.server.Start()
	if err != nil {
		panic(err)
	}
	defer h.server.Close()

	// create a RTSP description that contains a H264 format
	desc := &description.Session{
		Medias: []*description.Media{{
			Type: description.MediaTypeVideo,
			Formats: []format.Format{&format.H264{
				PayloadTyp:        96,
				PacketizationMode: 1,
			}},
		}},
	}

	// create a server stream
	h.stream = &gortsplib.ServerStream{
		Server: h.server,
		Desc:   desc,
	}
	err = h.stream.Initialize()
	if err != nil {
		panic(err)
	}
	defer h.stream.Close()

	// create file streamer
	r := &fileStreamer{stream: h.stream}
	err = r.initialize()
	if err != nil {
		panic(err)
	}
	defer r.close()

	// allow clients to connect
	h.mutex.Unlock()

	// wait until a fatal error
	log.Printf("server is ready on %s", h.server.RTSPAddress)
	panic(h.server.Wait())
}
```

<Tip>
  When serving a pre-built stream (not relay), hold the mutex locked during setup so no client can send a `DESCRIBE` before the stream is ready. Release it once `Initialize()` succeeds.
</Tip>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.