> ## 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.

# Quick Start

> Connect to an RTSP server and read a stream in under 30 lines of Go.

<Steps>
  <Step title="Install gortsplib">
    Add the library to your Go module:

    ```bash theme={null}
    go get github.com/bluenviron/gortsplib/v5
    ```

    <Tip>
      Go 1.25 or later is required. Run `go version` to confirm before proceeding.
    </Tip>
  </Step>

  <Step title="Play a stream">
    The following program connects to an RTSP server, describes all available media streams, sets them up, and logs every incoming RTP and RTCP packet.

    ```go client-play/main.go theme={null}
    // Package main contains an example.
    package main

    import (
    	"log"

    	"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"
    	"github.com/pion/rtcp"
    	"github.com/pion/rtp"
    )

    // This example shows how to:
    // 1. connect to a RTSP server.
    // 2. read all media streams on a path.

    func main() {
    	// parse URL
    	u, err := base.ParseURL("rtsp://myuser:mypass@localhost:8554/mystream")
    	if err != nil {
    		panic(err)
    	}

    	c := gortsplib.Client{
    		Scheme: u.Scheme,
    		Host:   u.Host,
    	}

    	// connect to the server
    	err = c.Start()
    	if err != nil {
    		panic(err)
    	}
    	defer c.Close()

    	// find available medias
    	desc, _, err := c.Describe(u)
    	if err != nil {
    		panic(err)
    	}

    	// setup all medias
    	err = c.SetupAll(desc.BaseURL, desc.Medias)
    	if err != nil {
    		panic(err)
    	}

    	// called when a RTP packet arrives
    	c.OnPacketRTPAny(func(medi *description.Media, _ format.Format, _ *rtp.Packet) {
    		log.Printf("RTP packet from media %v\n", medi)
    	})

    	// called when a RTCP packet arrives
    	c.OnPacketRTCPAny(func(medi *description.Media, pkt rtcp.Packet) {
    		log.Printf("RTCP packet from media %v, type %T\n", medi, pkt)
    	})

    	// start playing
    	_, err = c.Play(nil)
    	if err != nil {
    		panic(err)
    	}

    	// wait until a fatal error
    	panic(c.Wait())
    }
    ```

    Run it:

    ```bash theme={null}
    go run ./client-play/main.go
    ```

    You should see log lines like:

    ```
    2009/11/10 23:00:00 RTP packet from media &{video ...}
    2009/11/10 23:00:00 RTCP packet from media &{video ...}, type *rtcp.SenderReport
    ```

    <Tip>
      `defer c.Close()` is called immediately after `c.Start()` succeeds. Place it here — not after later calls — so the client is always cleaned up even when `Describe` or `SetupAll` returns an error.
    </Tip>
  </Step>

  <Step title="Record a stream">
    To publish a stream, describe the formats you want to send, call `StartRecording`, and then write RTP packets in a loop.

    ```go client-record/main.go theme={null}
    package main

    import (
    	"github.com/bluenviron/gortsplib/v5"
    	"github.com/bluenviron/gortsplib/v5/pkg/description"
    	"github.com/bluenviron/gortsplib/v5/pkg/format"
    	"github.com/pion/rtp"
    )

    func main() {
    	// describe the H264 format you want to publish
    	forma := &format.H264{
    		PayloadTyp:        96,
    		PacketizationMode: 1,
    	}
    	desc := &description.Session{
    		Medias: []*description.Media{{
    			Type:    description.MediaTypeVideo,
    			Formats: []format.Format{forma},
    		}},
    	}

    	// connect to the server, announce the format, and start recording
    	c := gortsplib.Client{}
    	err := c.StartRecording("rtsp://myuser:mypass@localhost:8554/mystream", desc)
    	if err != nil {
    		panic(err)
    	}
    	defer c.Close()

    	// write a single RTP packet to the first media
    	pkt := &rtp.Packet{
    		Header: rtp.Header{
    			PayloadType: 96,
    		},
    		Payload: []byte{0x00},
    	}
    	err = c.WritePacketRTP(desc.Medias[0], pkt)
    	if err != nil {
    		panic(err)
    	}
    }
    ```

    <Info>
      The `StartRecording` call combines connecting, announcing the session description, and entering record state in a single step. For more advanced scenarios — such as encoding RGBA frames to H264 before sending — see the full example at `examples/client-record-format-h264/main.go`.
    </Info>

    <Note>
      The H264 encoder example (`examples/client-record-format-h264/main.go`) carries a `//go:build cgo` tag and requires the FFmpeg development libraries. Pure RTP forwarding, as shown above, has no CGO dependency.
    </Note>
  </Step>

  <Step title="Build a server">
    A gortsplib server is built around a handler struct that implements the callbacks you care about. The example below accepts a single publisher and forwards its packets to all readers.

    ```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"
    )

    // This example shows how to:
    // 1. create a RTSP server which accepts plain connections.
    // 2. allow a single client to publish a stream.
    // 3. allow several clients to read the stream.

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

    // called when a connection is opened.
    func (sh *serverHandler) OnConnOpen(_ *gortsplib.ServerHandlerOnConnOpenCtx) {
    	log.Printf("conn opened")
    }

    // called when a connection is closed.
    func (sh *serverHandler) OnConnClose(ctx *gortsplib.ServerHandlerOnConnCloseCtx) {
    	log.Printf("conn closed (%v)", ctx.Error)
    }

    // called when a session is opened.
    func (sh *serverHandler) OnSessionOpen(_ *gortsplib.ServerHandlerOnSessionOpenCtx) {
    	log.Printf("session opened")
    }

    // called when a session is closed.
    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())
    }
    ```

    Run it:

    ```bash theme={null}
    go run ./server/main.go
    ```

    Expected output:

    ```
    2009/11/10 23:00:00 server is ready on :8554
    ```

    The server now accepts publisher connections on `:8554` and streams RTP packets to any number of readers. UDP transport is available on ports `8000`/`8001`, and multicast on `8002`/`8003`.

    <Tip>
      Implement only the handler methods you need. If you omit `OnAnnounce`, the server will reject all ANNOUNCE requests automatically.
    </Tip>
  </Step>
</Steps>

## Next steps

<CardGroup cols={2}>
  <Card title="Client guide" icon="play" href="/client/overview">
    Configure transport options, authentication, and per-format packet callbacks.
  </Card>

  <Card title="Server guide" icon="server" href="/server/overview">
    Handle authentication, multiple streams, and session lifecycle events.
  </Card>

  <Card title="RTP formats" icon="film" href="/formats/overview">
    Encode and decode H264, H265, AV1, AAC, Opus, and more.
  </Card>

  <Card title="Installation" icon="download" href="/installation">
    CGO requirements and version pinning details.
  </Card>
</CardGroup>


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