Ctrl K
ComunidadGuías…

internal/app/livestream/rtmp.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package livestream

import (
	"context"
	"io"
	"net"

	rtmp "github.com/yutopp/go-rtmp"
	rtmpmsg "github.com/yutopp/go-rtmp/message"
	"go.uber.org/zap"
)

// rtmpHandler is the callback for a single RTMP connection: on publish it authenticates
// with the stream key and binds the channel; media tags pass straight through FLV → ffmpeg.
// A connection allows only one publish.
type rtmpHandler struct {
	rtmp.DefaultHandler
	svc    *Service
	conn   net.Conn
	stream *stream
}

func (h *rtmpHandler) OnPublish(_ *rtmp.StreamContext, _ uint32, cmd *rtmpmsg.NetStreamPublish) error {
	if h.stream != nil {
		return errPublishRejected
	}
	st, err := h.svc.startPublish(context.Background(), cmd.PublishingName, h.conn)
	if err != nil {
		h.svc.log.Warn("rtmp publish rejected", zap.Error(err))
		return errPublishRejected
	}
	h.stream = st
	return nil
}

func (h *rtmpHandler) OnSetDataFrame(timestamp uint32, data *rtmpmsg.NetStreamSetDataFrame) error {
	if h.stream == nil {
		return nil
	}
	// onMetaData is passed through to ffmpeg verbatim (optional info; a write failure does not break the stream).
	_ = h.stream.writeTag(18, timestamp, data.Payload)
	return nil
}

func (h *rtmpHandler) OnAudio(timestamp uint32, payload io.Reader) error {
	return h.writeMedia(8, timestamp, payload)
}

func (h *rtmpHandler) OnVideo(timestamp uint32, payload io.Reader) error {
	return h.writeMedia(9, timestamp, payload)
}

func (h *rtmpHandler) writeMedia(tagType byte, timestamp uint32, payload io.Reader) error {
	if h.stream == nil {
		return nil
	}
	body, err := io.ReadAll(payload)
	if err != nil {
		return err
	}
	return h.stream.writeTag(tagType, timestamp, body)
}

func (h *rtmpHandler) OnClose() {
	if h.stream != nil {
		h.svc.endPublish(h.stream)
		h.stream = nil
	}
}