mirror of
https://github.com/AlexxIT/go2rtc.git
synced 2025-09-27 04:36:12 +08:00
128 lines
2.3 KiB
Go
128 lines
2.3 KiB
Go
package hls
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/AlexxIT/go2rtc/pkg/core"
|
|
"github.com/AlexxIT/go2rtc/pkg/mp4"
|
|
)
|
|
|
|
type Session struct {
|
|
cons core.Consumer
|
|
id string
|
|
template string
|
|
init []byte
|
|
buffer []byte
|
|
seq int
|
|
alive *time.Timer
|
|
mu sync.Mutex
|
|
}
|
|
|
|
func NewSession(cons core.Consumer) *Session {
|
|
s := &Session{
|
|
id: core.RandString(8, 62),
|
|
cons: cons,
|
|
}
|
|
|
|
// two segments important for Chromecast
|
|
if _, ok := cons.(*mp4.Consumer); ok {
|
|
s.template = `#EXTM3U
|
|
#EXT-X-VERSION:6
|
|
#EXT-X-TARGETDURATION:1
|
|
#EXT-X-MEDIA-SEQUENCE:%d
|
|
#EXT-X-MAP:URI="init.mp4?id=` + s.id + `"
|
|
#EXTINF:0.500,
|
|
segment.m4s?id=` + s.id + `&n=%d
|
|
#EXTINF:0.500,
|
|
segment.m4s?id=` + s.id + `&n=%d`
|
|
} else {
|
|
s.template = `#EXTM3U
|
|
#EXT-X-VERSION:3
|
|
#EXT-X-TARGETDURATION:1
|
|
#EXT-X-MEDIA-SEQUENCE:%d
|
|
#EXTINF:0.500,
|
|
segment.ts?id=` + s.id + `&n=%d
|
|
#EXTINF:0.500,
|
|
segment.ts?id=` + s.id + `&n=%d`
|
|
}
|
|
|
|
return s
|
|
}
|
|
|
|
func (s *Session) Write(p []byte) (n int, err error) {
|
|
s.mu.Lock()
|
|
if s.init == nil {
|
|
s.init = p
|
|
} else {
|
|
s.buffer = append(s.buffer, p...)
|
|
}
|
|
s.mu.Unlock()
|
|
return len(p), nil
|
|
}
|
|
|
|
func (s *Session) Run() {
|
|
_, _ = s.cons.(io.WriterTo).WriteTo(s)
|
|
}
|
|
|
|
func (s *Session) Main() []byte {
|
|
type withCodecs interface {
|
|
Codecs() []*core.Codec
|
|
}
|
|
|
|
codecs := mp4.MimeCodecs(s.cons.(withCodecs).Codecs())
|
|
codecs = strings.Replace(codecs, mp4.MimeFlac, "fLaC", 1)
|
|
|
|
// bandwidth important for Safari, codecs useful for smooth playback
|
|
return []byte(`#EXTM3U
|
|
#EXT-X-STREAM-INF:BANDWIDTH=192000,CODECS="` + codecs + `"
|
|
hls/playlist.m3u8?id=` + s.id)
|
|
}
|
|
|
|
func (s *Session) Playlist() []byte {
|
|
return []byte(fmt.Sprintf(s.template, s.seq, s.seq, s.seq+1))
|
|
}
|
|
|
|
func (s *Session) Init() (init []byte) {
|
|
for i := 0; i < 60 && init == nil; i++ {
|
|
if i > 0 {
|
|
time.Sleep(50 * time.Millisecond)
|
|
}
|
|
|
|
s.mu.Lock()
|
|
// return init only when have some buffer
|
|
if len(s.buffer) > 0 {
|
|
init = s.init
|
|
}
|
|
s.mu.Unlock()
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (s *Session) Segment() (segment []byte) {
|
|
for i := 0; i < 60 && segment == nil; i++ {
|
|
if i > 0 {
|
|
time.Sleep(50 * time.Millisecond)
|
|
}
|
|
|
|
s.mu.Lock()
|
|
if len(s.buffer) > 0 {
|
|
segment = s.buffer
|
|
if _, ok := s.cons.(*mp4.Consumer); ok {
|
|
s.buffer = nil
|
|
} else {
|
|
// for TS important to start new segment with init
|
|
s.buffer = s.init
|
|
}
|
|
s.seq++
|
|
}
|
|
s.mu.Unlock()
|
|
}
|
|
|
|
return
|
|
}
|