mediamtx/internal/hls/muxer_variant_mpegts_playlist.go
Alessandro Ros e115983296
Implement Low-Latency HLS (#938)
* add hlsVariant parameter

* hls: split muxer into variants

* hls: implement fmp4 segments

* hls muxer: implement low latency mode

* hls muxer: support audio with fmp4 mode

* hls muxer: rewrite file router

* hls muxer: implement preload hint

* hls muxer: add various error codes

* hls muxer: use explicit flags

* hls muxer: fix error in aac pts

* hls muxer: fix sudden freezes with video+audio

* hls muxer: skip empty parts

* hls muxer: fix video FPS

* hls muxer: add parameter hlsPartDuration

* hls muxer: refactor fmp4 muxer

* hls muxer: fix CAN-SKIP-UNTIL

* hls muxer: refactor code

* hls muxer: show only parts of last 2 segments

* hls muxer: implementa playlist delta updates

* hls muxer: change playlist content type

* hls muxer: improve video dts precision

* hls muxer: fix video sample flags

* hls muxer: improve iphone audio support

* hls muxer: improve mp4 timestamp precision

* hls muxer: add offset between pts and dts

* hls muxer: close muxer in case of error

* hls muxer: stop logging requests with the info level

* hls muxer: rename entry into sample

* hls muxer: compensate video dts error over time

* hls muxer: change default segment count

* hls muxer: add starting gap

* hls muxer: set default part duration to 200ms

* hls muxer: fix audio-only streams on ios

* hls muxer: add playsinline attribute to video tag of default web page

* hls muxer: keep mpegts as the default hls variant

* hls muxer: implement encryption

* hls muxer: rewrite dts estimation

* hls muxer: improve DTS precision

* hls muxer: use right SPS/PPS for each sample

* hls muxer: adjust part duration dynamically

* add comments

* update readme

* hls muxer: fix memory leak

* hls muxer: decrease ram consumption
2022-05-31 19:17:26 +02:00

147 lines
3.1 KiB
Go

package hls
import (
"bytes"
"io"
"math"
"net/http"
"strconv"
"strings"
"sync"
)
type muxerVariantMPEGTSPlaylist struct {
segmentCount int
mutex sync.Mutex
cond *sync.Cond
closed bool
segments []*muxerVariantMPEGTSSegment
segmentByName map[string]*muxerVariantMPEGTSSegment
segmentDeleteCount int
}
func newMuxerVariantMPEGTSPlaylist(segmentCount int) *muxerVariantMPEGTSPlaylist {
p := &muxerVariantMPEGTSPlaylist{
segmentCount: segmentCount,
segmentByName: make(map[string]*muxerVariantMPEGTSSegment),
}
p.cond = sync.NewCond(&p.mutex)
return p
}
func (p *muxerVariantMPEGTSPlaylist) close() {
func() {
p.mutex.Lock()
defer p.mutex.Unlock()
p.closed = true
}()
p.cond.Broadcast()
}
func (p *muxerVariantMPEGTSPlaylist) file(name string) *MuxerFileResponse {
switch {
case name == "stream.m3u8":
return p.playlistReader()
case strings.HasSuffix(name, ".ts"):
return p.segmentReader(name)
default:
return &MuxerFileResponse{Status: http.StatusNotFound}
}
}
func (p *muxerVariantMPEGTSPlaylist) playlist() io.Reader {
cnt := "#EXTM3U\n"
cnt += "#EXT-X-VERSION:3\n"
cnt += "#EXT-X-ALLOW-CACHE:NO\n"
targetDuration := func() uint {
ret := uint(0)
// EXTINF, when rounded to the nearest integer, must be <= EXT-X-TARGETDURATION
for _, s := range p.segments {
v2 := uint(math.Round(s.duration().Seconds()))
if v2 > ret {
ret = v2
}
}
return ret
}()
cnt += "#EXT-X-TARGETDURATION:" + strconv.FormatUint(uint64(targetDuration), 10) + "\n"
cnt += "#EXT-X-MEDIA-SEQUENCE:" + strconv.FormatInt(int64(p.segmentDeleteCount), 10) + "\n"
cnt += "\n"
for _, s := range p.segments {
cnt += "#EXT-X-PROGRAM-DATE-TIME:" + s.startTime.Format("2006-01-02T15:04:05.999Z07:00") + "\n" +
"#EXTINF:" + strconv.FormatFloat(s.duration().Seconds(), 'f', -1, 64) + ",\n" +
s.name + ".ts\n"
}
return bytes.NewReader([]byte(cnt))
}
func (p *muxerVariantMPEGTSPlaylist) playlistReader() *MuxerFileResponse {
p.mutex.Lock()
defer p.mutex.Unlock()
if !p.closed && len(p.segments) == 0 {
p.cond.Wait()
}
if p.closed {
return &MuxerFileResponse{Status: http.StatusInternalServerError}
}
return &MuxerFileResponse{
Status: http.StatusOK,
Header: map[string]string{
"Content-Type": `audio/mpegURL`,
},
Body: p.playlist(),
}
}
func (p *muxerVariantMPEGTSPlaylist) segmentReader(fname string) *MuxerFileResponse {
base := strings.TrimSuffix(fname, ".ts")
p.mutex.Lock()
f, ok := p.segmentByName[base]
p.mutex.Unlock()
if !ok {
return &MuxerFileResponse{Status: http.StatusNotFound}
}
return &MuxerFileResponse{
Status: http.StatusOK,
Header: map[string]string{
"Content-Type": "video/MP2T",
},
Body: f.reader(),
}
}
func (p *muxerVariantMPEGTSPlaylist) pushSegment(t *muxerVariantMPEGTSSegment) {
func() {
p.mutex.Lock()
defer p.mutex.Unlock()
p.segmentByName[t.name] = t
p.segments = append(p.segments, t)
if len(p.segments) > p.segmentCount {
delete(p.segmentByName, p.segments[0].name)
p.segments = p.segments[1:]
p.segmentDeleteCount++
}
}()
p.cond.Broadcast()
}