121 lines
2.3 KiB
Go
121 lines
2.3 KiB
Go
package playlist
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"time"
|
|
|
|
segmentpkg "segmento/internal/segment"
|
|
)
|
|
|
|
const DefaultPlaylistDuration = 20 * time.Second
|
|
|
|
type segment struct {
|
|
segmentpkg.Segment
|
|
seqId int
|
|
}
|
|
|
|
type PlaylistListener interface {
|
|
OnUpdate(p Playlist)
|
|
}
|
|
|
|
type Playlist interface {
|
|
Duration() time.Duration
|
|
TargetDuration() time.Duration
|
|
Render() string
|
|
ReadSegments(chan *segmentpkg.Segment) error
|
|
AddListener(l PlaylistListener)
|
|
}
|
|
|
|
type MediaPlaylist struct {
|
|
Segments []*segment
|
|
Listeners []PlaylistListener
|
|
|
|
seqId int
|
|
}
|
|
|
|
func NewMediaPlaylist() *MediaPlaylist {
|
|
return &MediaPlaylist{
|
|
Segments: make([]*segment, 0, 10),
|
|
Listeners: make([]PlaylistListener, 0),
|
|
}
|
|
}
|
|
|
|
func (p *MediaPlaylist) nextSeqId() int {
|
|
next := p.seqId
|
|
p.seqId++
|
|
return next
|
|
}
|
|
|
|
func (p *MediaPlaylist) Duration() time.Duration {
|
|
return p.durationOf(p.Segments)
|
|
}
|
|
|
|
func (p *MediaPlaylist) TargetDuration() time.Duration {
|
|
return DefaultPlaylistDuration
|
|
}
|
|
|
|
// pass by pointer or value here?
|
|
// I think value is better, the struct is tiny and only contains a pointer
|
|
// to the data buffer
|
|
func (p *MediaPlaylist) AddSegment(s *segmentpkg.Segment) error {
|
|
nextId := p.nextSeqId()
|
|
ss := segment{*s, nextId}
|
|
p.Segments = append(p.Segments, &ss)
|
|
|
|
if len(p.Segments) > 1 {
|
|
for {
|
|
if p.durationOf(p.Segments[1:]) > p.TargetDuration() {
|
|
p.Segments = p.Segments[1:]
|
|
}
|
|
break
|
|
}
|
|
}
|
|
|
|
p.updateListeners()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *MediaPlaylist) durationOf(ss []*segment) time.Duration {
|
|
var t time.Duration
|
|
for _, s := range ss {
|
|
t += s.Duration()
|
|
}
|
|
return t
|
|
}
|
|
|
|
func (p *MediaPlaylist) Render() string {
|
|
var r string
|
|
r += "#EXTM3U\n"
|
|
r += "#EXT-X-VERSION:3\n"
|
|
r += "#EXT-X-TARGETDURATION:3\n" // TODO
|
|
for _, s := range p.Segments {
|
|
r += fmt.Sprintf("#EXTINF:%.05f\n", float32(s.Duration())/float32(time.Second))
|
|
r += "http://www.example.com/x.mp3\n"
|
|
}
|
|
r += "#EXT-X-ENDLIST"
|
|
return r
|
|
}
|
|
|
|
// Listeners
|
|
|
|
func (p *MediaPlaylist) AddListener(l PlaylistListener) {
|
|
p.Listeners = append(p.Listeners, l)
|
|
}
|
|
|
|
func (p *MediaPlaylist) updateListeners() {
|
|
for _, l := range p.Listeners {
|
|
l.OnUpdate(p)
|
|
}
|
|
}
|
|
|
|
func (p *MediaPlaylist) ReadSegments(segments chan *segmentpkg.Segment) error {
|
|
for s := range segments {
|
|
log.Println("got segment with duration", s.Duration(), "and len", s.Len(), "bytes")
|
|
p.AddSegment(s)
|
|
}
|
|
log.Println("exiting ReadSegments")
|
|
return nil
|
|
}
|