package rtsp import ( "context" "fmt" "log" "sync" "github.com/pion/rtp" "github.com/bluenviron/gortsplib/v4" "github.com/bluenviron/gortsplib/v4/pkg/base" "github.com/bluenviron/gortsplib/v4/pkg/description" "github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/jonas-koeritz/actioncam/libipcamera" ) type Server struct { stream *gortsplib.ServerStream media *description.Media gs *gortsplib.Server ctx context.Context cancel context.CancelFunc wg sync.WaitGroup } // CreateServer now requires relay as input func CreateServer(parentCtx context.Context, host string, port int, relay *libipcamera.RTPRelay) (*Server, error) { ctx, cancel := context.WithCancel(parentCtx) stream, media, err := newH264Stream() if err != nil { cancel() return nil, fmt.Errorf("create H264 stream: %w", err) } srv := &Server{ ctx: ctx, cancel: cancel, stream: stream, media: media, } h := &handler{srv: srv} gs := &gortsplib.Server{ Handler: h, RTSPAddress: fmt.Sprintf("%s:%d", host, port), } srv.gs = gs // 🧠 Pump from relay to RTSP srv.wg.Add(1) go srv.relayPump(relay) go func() { log.Printf("RTSP server ready on rtsp://%s:%d/", host, port) err := gs.StartAndWait() if err != nil { log.Printf("RTSP stopped: %v", err) } cancel() }() return srv, nil } func (s *Server) Close() { s.cancel() if s.gs != nil { s.gs.Close() } s.wg.Wait() if s.stream != nil { s.stream.Close() } } // Relay pump: consumes RTP from channel func (s *Server) relayPump(relay *libipcamera.RTPRelay) { defer s.wg.Done() for { select { case <-s.ctx.Done(): return case frame, ok := <-relay.Output: if !ok { return } // Build RTP packet var pkt rtp.Packet if err := pkt.Unmarshal(frame.Payload); err != nil { continue } s.stream.WritePacketRTP(s.media, &pkt) } } } // Handler below… type handler struct{ srv *Server } func (h *handler) OnDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx) (*base.Response, *gortsplib.ServerStream, error) { return &base.Response{StatusCode: base.StatusOK}, h.srv.stream, nil } func (h *handler) OnSetup(ctx *gortsplib.ServerHandlerOnSetupCtx) (*base.Response, *gortsplib.ServerStream, error) { return &base.Response{StatusCode: base.StatusOK}, h.srv.stream, nil } func (h *handler) OnPlay(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Response, error) { return &base.Response{StatusCode: base.StatusOK}, nil } // Build simple baseline H264 SDP func newH264Stream() (*gortsplib.ServerStream, *description.Media, error) { h264 := &format.H264{ PayloadTyp: 96, PacketizationMode: 1, SPS: []byte{0x67, 0x42, 0xC0, 0x1F, 0x96, 0x54, 0x05, 0x01, 0xED, 0x00, 0xF0, 0x88, 0x45, 0x80}, PPS: []byte{0x68, 0xCE, 0x38, 0x80}, } media := &description.Media{ Type: description.MediaTypeVideo, Formats: []format.Format{h264}, } desc := &description.Session{ Medias: []*description.Media{media}, } stream := &gortsplib.ServerStream{Desc: desc} if err := stream.Initialize(); err != nil { return nil, nil, err } return stream, media, nil }