mirror of
https://github.com/bluenviron/mediamtx
synced 2025-01-25 16:33:39 +00:00
e5ab731d14
* hls source: support fMP4s video streams * hls source: start reading live streams from (end of playlist - starting point) * hls client: wait processing of current fMP4 segment before downloading another one * hls client: support fmp4 trun boxes with default sample duration, flags and size * hls client: merge fmp4 init file reader and writer * hls client: merge fmp4 part reader and writer * hls client: improve precision of go <-> mp4 time conversion * hls client: fix esds generation in go-mp4 * hls client: support audio in separate playlist * hls client: support an arbitrary number of tracks in fmp4 init files * hls client: support EXT-X-BYTERANGE * hls client: support fmp4 segments with multiple parts at once * hls client: support an arbitrary number of mpeg-ts tracks * hls client: synchronize tracks around a primary track * update go-mp4 * hls: synchronize track reproduction around a leading one * hls client: reset stream if playback is too late * hls client: add limit on DTS-RTC difference * hls client: support again streams that don't provide codecs in master playlist
187 lines
3.8 KiB
Go
187 lines
3.8 KiB
Go
package hls
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/aler9/gortsplib"
|
|
)
|
|
|
|
type partsReader struct {
|
|
parts []*muxerVariantFMP4Part
|
|
curPart int
|
|
curPos int
|
|
}
|
|
|
|
func (mbr *partsReader) Read(p []byte) (int, error) {
|
|
n := 0
|
|
lenp := len(p)
|
|
|
|
for {
|
|
if mbr.curPart >= len(mbr.parts) {
|
|
return n, io.EOF
|
|
}
|
|
|
|
copied := copy(p[n:], mbr.parts[mbr.curPart].content[mbr.curPos:])
|
|
mbr.curPos += copied
|
|
n += copied
|
|
|
|
if mbr.curPos == len(mbr.parts[mbr.curPart].content) {
|
|
mbr.curPart++
|
|
mbr.curPos = 0
|
|
}
|
|
|
|
if n == lenp {
|
|
return n, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
type muxerVariantFMP4Segment struct {
|
|
lowLatency bool
|
|
id uint64
|
|
startTime time.Time
|
|
startDTS time.Duration
|
|
segmentMaxSize uint64
|
|
videoTrack *gortsplib.TrackH264
|
|
audioTrack *gortsplib.TrackMPEG4Audio
|
|
genPartID func() uint64
|
|
onPartFinalized func(*muxerVariantFMP4Part)
|
|
|
|
name string
|
|
size uint64
|
|
parts []*muxerVariantFMP4Part
|
|
currentPart *muxerVariantFMP4Part
|
|
renderedDuration time.Duration
|
|
}
|
|
|
|
func newMuxerVariantFMP4Segment(
|
|
lowLatency bool,
|
|
id uint64,
|
|
startTime time.Time,
|
|
startDTS time.Duration,
|
|
segmentMaxSize uint64,
|
|
videoTrack *gortsplib.TrackH264,
|
|
audioTrack *gortsplib.TrackMPEG4Audio,
|
|
genPartID func() uint64,
|
|
onPartFinalized func(*muxerVariantFMP4Part),
|
|
) *muxerVariantFMP4Segment {
|
|
s := &muxerVariantFMP4Segment{
|
|
lowLatency: lowLatency,
|
|
id: id,
|
|
startTime: startTime,
|
|
startDTS: startDTS,
|
|
segmentMaxSize: segmentMaxSize,
|
|
videoTrack: videoTrack,
|
|
audioTrack: audioTrack,
|
|
genPartID: genPartID,
|
|
onPartFinalized: onPartFinalized,
|
|
name: "seg" + strconv.FormatUint(id, 10),
|
|
}
|
|
|
|
s.currentPart = newMuxerVariantFMP4Part(
|
|
s.videoTrack,
|
|
s.audioTrack,
|
|
s.genPartID(),
|
|
)
|
|
|
|
return s
|
|
}
|
|
|
|
func (s *muxerVariantFMP4Segment) reader() io.Reader {
|
|
return &partsReader{parts: s.parts}
|
|
}
|
|
|
|
func (s *muxerVariantFMP4Segment) getRenderedDuration() time.Duration {
|
|
return s.renderedDuration
|
|
}
|
|
|
|
func (s *muxerVariantFMP4Segment) finalize(
|
|
nextVideoSampleDTS time.Duration,
|
|
) error {
|
|
err := s.currentPart.finalize()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if s.currentPart.content != nil {
|
|
s.onPartFinalized(s.currentPart)
|
|
s.parts = append(s.parts, s.currentPart)
|
|
}
|
|
|
|
s.currentPart = nil
|
|
|
|
if s.videoTrack != nil {
|
|
s.renderedDuration = nextVideoSampleDTS - s.startDTS
|
|
} else {
|
|
s.renderedDuration = 0
|
|
for _, pa := range s.parts {
|
|
s.renderedDuration += pa.renderedDuration
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *muxerVariantFMP4Segment) writeH264(sample *augmentedVideoSample, adjustedPartDuration time.Duration) error {
|
|
size := uint64(len(sample.Payload))
|
|
if (s.size + size) > s.segmentMaxSize {
|
|
return fmt.Errorf("reached maximum segment size")
|
|
}
|
|
s.size += size
|
|
|
|
s.currentPart.writeH264(sample)
|
|
|
|
// switch part
|
|
if s.lowLatency &&
|
|
s.currentPart.duration() >= adjustedPartDuration {
|
|
err := s.currentPart.finalize()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
s.parts = append(s.parts, s.currentPart)
|
|
s.onPartFinalized(s.currentPart)
|
|
|
|
s.currentPart = newMuxerVariantFMP4Part(
|
|
s.videoTrack,
|
|
s.audioTrack,
|
|
s.genPartID(),
|
|
)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *muxerVariantFMP4Segment) writeAAC(sample *augmentedAudioSample, adjustedPartDuration time.Duration) error {
|
|
size := uint64(len(sample.Payload))
|
|
if (s.size + size) > s.segmentMaxSize {
|
|
return fmt.Errorf("reached maximum segment size")
|
|
}
|
|
s.size += size
|
|
|
|
s.currentPart.writeAAC(sample)
|
|
|
|
// switch part
|
|
if s.lowLatency && s.videoTrack == nil &&
|
|
s.currentPart.duration() >= adjustedPartDuration {
|
|
err := s.currentPart.finalize()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
s.parts = append(s.parts, s.currentPart)
|
|
s.onPartFinalized(s.currentPart)
|
|
|
|
s.currentPart = newMuxerVariantFMP4Part(
|
|
s.videoTrack,
|
|
s.audioTrack,
|
|
s.genPartID(),
|
|
)
|
|
}
|
|
|
|
return nil
|
|
}
|