demo live stream

This commit is contained in:
2026-04-23 23:49:10 +01:00
parent bee7869af4
commit 285533c13e
9 changed files with 837 additions and 16 deletions

View File

@@ -10,6 +10,12 @@ import (
"github.com/go-gst/go-gst/gst/app"
)
const (
liveFrameTypeInit = 0x01
liveFrameTypeMedia = 0x02
liveFrameTypeError = 0x03
)
const (
liveSampleTimeout = 200 * time.Millisecond
liveSampleBufferSize = 5
@@ -97,7 +103,7 @@ func (p *CameraPipeline) addLiveBranch(branchTimeout time.Duration) error {
if err != nil {
return fmt.Errorf("failed to create mp4mux element: %w", err)
}
if err := mp4Mux.SetProperty("fragment-duration", uint(100*time.Millisecond.Nanoseconds())); err != nil {
if err := mp4Mux.SetProperty("fragment-duration", uint(100)); err != nil {
return fmt.Errorf("failed to set mp4mux fragment-duration property: %w", err)
}
if err := setGlibValueProperty(mp4Mux, "fragment-mode", 0); err != nil { // dash-or-mss
@@ -193,6 +199,8 @@ SamplerLoop:
func (p *CameraPipeline) liveManager(ctx context.Context) {
active := false
var initSeg []byte
assembler := newFMP4Assembler()
var lastMediaEmit time.Time
subscribers := &subscribersManager{m: make(map[int]subscriber), nextID: 0}
samples := make(chan []byte, liveSampleBufferSize)
samplerCtx, samplerCancel := context.WithCancel(ctx)
@@ -217,6 +225,9 @@ func (p *CameraPipeline) liveManager(ctx context.Context) {
case req := <-p.liveSubscribe:
if !active {
p.log.Info("Activating live branch")
initSeg = nil
assembler = newFMP4Assembler()
lastMediaEmit = time.Time{}
if err := p.liveVValve.SetProperty("drop", false); err != nil {
req.Result <- subscribeRes{Err: fmt.Errorf("failed to activate video valve: %w", err)}
continue
@@ -244,10 +255,29 @@ func (p *CameraPipeline) liveManager(ctx context.Context) {
case req := <-p.liveUnsubscribe:
subscribers.RemoveSubscriber(req.Id)
case data := <-samples:
// TODO : init segment
p.log.Debug("got video sample")
chunks, err := assembler.Push(data)
if err != nil {
p.log.Warn("Failed to assemble live fMP4 chunks", "error", err)
continue
}
numSamples += 1
subscribers.Broadcast(data)
for _, chunk := range chunks {
framed := marshalLiveFrame(chunk.FrameType, chunk.Payload)
switch chunk.FrameType {
case liveFrameTypeInit:
initSeg = framed
p.log.Debug("Emitted init segment", "size", len(chunk.Payload))
case liveFrameTypeMedia:
now := time.Now()
if !lastMediaEmit.IsZero() {
p.log.Debug("Emitted media segment", "size", len(chunk.Payload), "gap", now.Sub(lastMediaEmit))
} else {
p.log.Debug("Emitted media segment", "size", len(chunk.Payload))
}
lastMediaEmit = now
}
subscribers.Broadcast(framed)
}
case <-timeout:
p.log.Info("Deactivating live branch due to inactivity")
if err := p.liveVValve.SetProperty("drop", true); err != nil {
@@ -263,6 +293,13 @@ func (p *CameraPipeline) liveManager(ctx context.Context) {
}
}
func marshalLiveFrame(frameType byte, payload []byte) []byte {
framed := make([]byte, len(payload)+1)
framed[0] = frameType
copy(framed[1:], payload)
return framed
}
func (p *CameraPipeline) LiveSubscribe(timeout time.Duration, bufferSize int) (id int, stream <-chan *bytes.Reader, err error) {
result := make(chan subscribeRes)
req := subscribeReq{