2020-07-08 17:31:57 +00:00
|
|
|
// Package media contains logic related to parsing and segmenting media
|
|
|
|
// streams, such as MP3 streams.
|
|
|
|
//
|
|
|
|
// It is depended upon by the playlist package.
|
|
|
|
package media
|
2020-07-07 14:35:20 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"io"
|
|
|
|
"log"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/tcolgate/mp3"
|
|
|
|
)
|
|
|
|
|
|
|
|
const DefaultTargetDuration = 3 * time.Second
|
|
|
|
|
2020-07-08 17:31:57 +00:00
|
|
|
type Segmenter interface {
|
|
|
|
Segment(r io.Reader) (chan *Segment, error)
|
|
|
|
}
|
|
|
|
|
|
|
|
type SegmentPublisher interface {
|
|
|
|
Publish(s *Segment) (string, error)
|
|
|
|
}
|
|
|
|
|
2020-07-07 14:35:20 +00:00
|
|
|
type Segment struct {
|
|
|
|
duration time.Duration
|
2020-07-08 17:31:57 +00:00
|
|
|
targetDuration time.Duration
|
2020-07-07 14:35:20 +00:00
|
|
|
data *bytes.Buffer
|
|
|
|
}
|
|
|
|
|
|
|
|
// Initialize a new Segment struct. The capacity is the initial maximum capacity of the internal
|
|
|
|
// buffer, in bytes. It should be initialized with a value greater than the expected maximum buffer size,
|
|
|
|
// depending on the implementation.
|
|
|
|
func NewSegment(targetDuration time.Duration, capacity int) *Segment {
|
|
|
|
return &Segment{
|
|
|
|
data: bytes.NewBuffer(make([]byte, 0, capacity)),
|
|
|
|
duration: 0,
|
|
|
|
targetDuration: targetDuration,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Segment) ReadFrom(r io.Reader) (n int64, err error) {
|
|
|
|
return s.data.ReadFrom(r)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Segment) IncrementDuration(d time.Duration) time.Duration {
|
|
|
|
s.duration += d
|
|
|
|
return s.duration
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Segment) CanWrite(d time.Duration) bool {
|
|
|
|
return s.targetDuration-s.duration >= d
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Segment) Duration() time.Duration {
|
|
|
|
return s.duration
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Segment) Len() int {
|
|
|
|
return s.data.Len()
|
|
|
|
}
|
|
|
|
|
2020-07-08 17:31:57 +00:00
|
|
|
type MP3Segmenter struct {
|
2020-07-07 14:35:20 +00:00
|
|
|
decoder *mp3.Decoder
|
|
|
|
}
|
|
|
|
|
2020-07-08 17:31:57 +00:00
|
|
|
func (s *MP3Segmenter) Segment(r io.Reader) (chan *Segment, error) {
|
2020-07-07 14:35:20 +00:00
|
|
|
c := make(chan *Segment)
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
d := mp3.NewDecoder(r)
|
|
|
|
|
|
|
|
var (
|
|
|
|
v mp3.Frame
|
|
|
|
skipped int
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
s *Segment
|
|
|
|
)
|
|
|
|
|
|
|
|
for {
|
|
|
|
if err := d.Decode(&v, &skipped); err != nil {
|
|
|
|
if err == io.EOF {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if s != nil && !s.CanWrite(v.Duration()) {
|
|
|
|
// publish the current segment, and initialize a new one.
|
|
|
|
c <- s
|
|
|
|
s = nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if s == nil {
|
|
|
|
// TODO what is a good initial capacity?
|
|
|
|
s = NewSegment(DefaultTargetDuration, 1024)
|
|
|
|
}
|
|
|
|
|
|
|
|
n, err := s.ReadFrom(v.Reader())
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err) // TODO: some proper error handling
|
|
|
|
}
|
|
|
|
|
|
|
|
if n != int64(v.Size()) {
|
|
|
|
// TODO would this ever happen? What should IncrementDuration be called with?
|
|
|
|
log.Fatal("unexpeced frame size, want = ", v.Size(), "got = ", n)
|
|
|
|
}
|
|
|
|
|
|
|
|
s.IncrementDuration(v.Duration())
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
return c, nil
|
|
|
|
}
|
|
|
|
|
2020-07-08 17:31:57 +00:00
|
|
|
func NewMP3Segmenter() *MP3Segmenter {
|
|
|
|
return &MP3Segmenter{}
|
|
|
|
}
|
|
|
|
|
|
|
|
type FakePublisher struct{}
|
|
|
|
|
|
|
|
func (p *FakePublisher) Publish(s *Segment) (string, error) {
|
|
|
|
return "https://www.example.com/segment.mp3", nil
|
2020-07-07 14:35:20 +00:00
|
|
|
}
|