add manifest

This commit is contained in:
Eric Tang
2017-09-24 00:24:09 -04:00
parent b6db39dbd4
commit 3d955ba45e
9 changed files with 139 additions and 46 deletions
+44 -13
View File
@@ -2,6 +2,8 @@ package stream
import (
"errors"
"fmt"
"strings"
"github.com/ericxtang/m3u8"
"github.com/golang/glog"
@@ -11,18 +13,18 @@ var ErrVideoManifest = errors.New("ErrVideoManifest")
type BasicHLSVideoManifest struct {
streamMap map[string]HLSVideoStream
variantMap map[string]*m3u8.Variant
manifestCache *m3u8.MasterPlaylist
id string
winSize uint
}
func NewBasicHLSVideoManifest(id string, wSize uint) *BasicHLSVideoManifest {
func NewBasicHLSVideoManifest(id string) *BasicHLSVideoManifest {
pl := m3u8.NewMasterPlaylist()
return &BasicHLSVideoManifest{
streamMap: make(map[string]HLSVideoStream),
variantMap: make(map[string]*m3u8.Variant),
manifestCache: pl,
id: id,
winSize: wSize,
}
}
@@ -42,26 +44,53 @@ func (m *BasicHLSVideoManifest) GetVideoStream(strmID string) (HLSVideoStream, e
return strm, nil
}
func (m *BasicHLSVideoManifest) AddVideoStream(strmID string, variant *m3u8.Variant) (*BasicHLSVideoStream, error) {
_, ok := m.streamMap[strmID]
func (m *BasicHLSVideoManifest) GetVideoStreams() []HLSVideoStream {
res := []HLSVideoStream{}
for _, s := range m.streamMap {
res = append(res, s)
}
return res
}
func (m *BasicHLSVideoManifest) AddVideoStream(strm HLSVideoStream, variant *m3u8.Variant) error {
_, ok := m.streamMap[strm.GetStreamID()]
if ok {
return nil, ErrVideoManifest
return ErrVideoManifest
}
//Check if the same Bandwidth & Resolution already exists
for _, strm := range m.streamMap {
v := strm.GetStreamVariant()
for _, v := range m.variantMap {
// v := mStrm.GetStreamVariant()
if v.Bandwidth == variant.Bandwidth && v.Resolution == variant.Resolution {
// if v.Bandwidth == strm.GetStreamVariant().Bandwidth && v.Resolution == strm.GetStreamVariant().Resolution {
glog.Errorf("Variant with Bandwidth %v and Resolution %v already exists", v.Bandwidth, v.Resolution)
return nil, ErrVideoManifest
return ErrVideoManifest
}
}
//Add to the map
// m.manifestCache.Append(strm.GetStreamVariant().URI, strm.GetStreamVariant().Chunklist, strm.GetStreamVariant().VariantParams)
m.manifestCache.Append(variant.URI, variant.Chunklist, variant.VariantParams)
strm := NewBasicHLSVideoStream(strmID, variant, m.winSize)
m.streamMap[strmID] = strm
return strm, nil
m.streamMap[strm.GetStreamID()] = strm
m.variantMap[strm.GetStreamID()] = variant
return nil
}
func (m *BasicHLSVideoManifest) GetStreamVariant(strmID string) (*m3u8.Variant, error) {
//Try from the variant map
v, ok := m.variantMap[strmID]
if ok {
return v, nil
}
//Try from the playlist itself
for _, v := range m.manifestCache.Variants {
vsid := strings.Split(v.URI, ".")[0]
if vsid == strmID {
return v, nil
}
}
return nil, ErrNotFound
}
func (m *BasicHLSVideoManifest) DeleteVideoStream(strmID string) error {
@@ -69,4 +98,6 @@ func (m *BasicHLSVideoManifest) DeleteVideoStream(strmID string) error {
return nil
}
func (m *BasicHLSVideoManifest) String() string { return "" }
func (m BasicHLSVideoManifest) String() string {
return fmt.Sprintf("id:%v, streams:%v", m.id, m.streamMap)
}
+7 -7
View File
@@ -7,9 +7,9 @@ import (
)
func TestAddAndRemove(t *testing.T) {
manifest := NewBasicHLSVideoManifest("test_m", 3)
strm, err := manifest.AddVideoStream("test_s", &m3u8.Variant{URI: "test_s", Chunklist: nil, VariantParams: m3u8.VariantParams{Bandwidth: 100}})
if err != nil {
manifest := NewBasicHLSVideoManifest("test_m")
strm := NewBasicHLSVideoStream("test_s", DefaultHLSStreamWin)
if err := manifest.AddVideoStream(strm, &m3u8.Variant{URI: "test_s", Chunklist: nil, VariantParams: m3u8.VariantParams{Bandwidth: 100}}); err != nil {
t.Errorf("Error: %v", err)
}
ml, err := manifest.GetManifest()
@@ -65,12 +65,12 @@ func TestAddAndRemove(t *testing.T) {
//Add a stream
pl, _ = m3u8.NewMediaPlaylist(3, 10)
_, err = manifest.AddVideoStream("test2", &m3u8.Variant{URI: "test2.m3u8", Chunklist: pl, VariantParams: m3u8.VariantParams{Bandwidth: 10, Resolution: "10x10"}})
if err != nil {
strm2 := NewBasicHLSVideoStream("test2", DefaultHLSStreamWin)
if err := manifest.AddVideoStream(strm2, &m3u8.Variant{URI: "test2.m3u8", Chunklist: pl, VariantParams: m3u8.VariantParams{Bandwidth: 10, Resolution: "10x10"}}); err != nil {
t.Errorf("Error adding media playlist: %v", err)
}
_, err = manifest.AddVideoStream("test3", &m3u8.Variant{URI: "test3.m3u8", Chunklist: pl, VariantParams: m3u8.VariantParams{Bandwidth: 10, Resolution: "10x10"}})
if err == nil {
strm3 := NewBasicHLSVideoStream("test3", DefaultHLSStreamWin)
if err := manifest.AddVideoStream(strm3, &m3u8.Variant{URI: "test3.m3u8", Chunklist: pl, VariantParams: m3u8.VariantParams{Bandwidth: 10, Resolution: "10x10"}}); err == nil {
t.Errorf("Expecting error because of duplicate variant params")
}
vstrm, err := manifest.GetVideoStream("wrongName")
+9 -10
View File
@@ -11,19 +11,18 @@ import (
"github.com/livepeer/go-livepeer/common"
)
const DefaultMediaPlLen = uint(500)
const DefaultHLSStreamCap = uint(500)
const DefaultHLSStreamWin = uint(3)
// const DefaultMediaWinLen = uint(5)
const DefaultSegWaitTime = time.Second * 10
const SegWaitInterval = time.Second
var ErrAddVariant = errors.New("ErrAddVariant")
var ErrAddHLSSegment = errors.New("ErrAddHLSSegment")
//BasicHLSVideoStream is a basic implementation of HLSVideoStream
type BasicHLSVideoStream struct {
plCache *m3u8.MediaPlaylist //StrmID -> MediaPlaylist
variant *m3u8.Variant
sqMap map[string]*HLSSegment
lock sync.Locker
strmID string
@@ -31,15 +30,15 @@ type BasicHLSVideoStream struct {
winSize uint
}
func NewBasicHLSVideoStream(strmID string, variant *m3u8.Variant, wSize uint) *BasicHLSVideoStream {
pl, err := m3u8.NewMediaPlaylist(wSize, DefaultMediaPlLen)
func NewBasicHLSVideoStream(strmID string, wSize uint) *BasicHLSVideoStream {
pl, err := m3u8.NewMediaPlaylist(wSize, DefaultHLSStreamCap)
if err != nil {
return nil
}
return &BasicHLSVideoStream{
plCache: pl,
variant: variant,
// variant: variant,
sqMap: make(map[string]*HLSSegment),
lock: &sync.Mutex{},
strmID: strmID,
@@ -58,7 +57,7 @@ func (s *BasicHLSVideoStream) GetStreamID() string { return s.strmID }
//GetStreamFormat always returns HLS
func (s *BasicHLSVideoStream) GetStreamFormat() VideoFormat { return HLS }
//GetVariantPlaylist returns the media playlist represented by the streamID
//GetStreamPlaylist returns the media playlist represented by the streamID
func (s *BasicHLSVideoStream) GetStreamPlaylist() (*m3u8.MediaPlaylist, error) {
if s.plCache.Count() < s.winSize {
return nil, nil
@@ -67,9 +66,9 @@ func (s *BasicHLSVideoStream) GetStreamPlaylist() (*m3u8.MediaPlaylist, error) {
return s.plCache, nil
}
func (s *BasicHLSVideoStream) GetStreamVariant() *m3u8.Variant {
return s.variant
}
// func (s *BasicHLSVideoStream) GetStreamVariant() *m3u8.Variant {
// return s.variant
// }
//GetHLSSegment gets the HLS segment. It blocks until something is found, or timeout happens.
func (s *BasicHLSVideoStream) GetHLSSegment(segName string) (*HLSSegment, error) {
+28
View File
@@ -16,6 +16,7 @@ type BasicRTMPVideoStream struct {
streamID string
buffer *streamBuffer
RTMPTimeout time.Duration
header []av.CodecData
}
//NewBasicRTMPVideoStream creates a new BasicRTMPVideoStream. The default RTMPTimeout is set to 10 milliseconds because we assume all RTMP streams are local.
@@ -77,6 +78,13 @@ func (s *BasicRTMPVideoStream) ReadRTMPFromStream(ctx context.Context, dst av.Mu
func (s *BasicRTMPVideoStream) WriteRTMPToStream(ctx context.Context, src av.DemuxCloser) error {
defer src.Close()
//Set header in case we want to use it.
h, err := src.Streams()
if err != nil {
return err
}
s.header = h
c := make(chan error, 1)
go func() {
c <- func() error {
@@ -125,3 +133,23 @@ func (s *BasicRTMPVideoStream) WriteRTMPToStream(ctx context.Context, src av.Dem
func (s BasicRTMPVideoStream) String() string {
return fmt.Sprintf("StreamID: %v, Type: %v", s.GetStreamID(), s.GetStreamFormat())
}
func (s BasicRTMPVideoStream) Height() int {
for _, cd := range s.header {
if cd.Type().IsVideo() {
return cd.(av.VideoCodecData).Height()
}
}
return 0
}
func (s BasicRTMPVideoStream) Width() int {
for _, cd := range s.header {
if cd.Type().IsVideo() {
return cd.(av.VideoCodecData).Width()
}
}
return 0
}
+18 -2
View File
@@ -5,8 +5,10 @@ import (
"errors"
"io"
"testing"
"time"
"github.com/nareix/joy4/av"
"github.com/nareix/joy4/codec/h264parser"
)
//Testing WriteRTMP errors
@@ -69,8 +71,10 @@ type PacketsDemuxer struct {
c *Counter
}
func (d PacketsDemuxer) Close() error { return nil }
func (d PacketsDemuxer) Streams() ([]av.CodecData, error) { return nil, nil }
func (d PacketsDemuxer) Close() error { return nil }
func (d PacketsDemuxer) Streams() ([]av.CodecData, error) {
return []av.CodecData{h264parser.CodecData{}}, nil
}
func (d PacketsDemuxer) ReadPacket() (av.Packet, error) {
if d.c.Count == 10 {
return av.Packet{Data: []byte{0, 0}}, io.EOF
@@ -93,6 +97,18 @@ func TestWriteBasicRTMP(t *testing.T) {
t.Error("Expecting buffer length to be 12, but got: ", stream.buffer.len())
}
start := time.Now()
for time.Since(start) < time.Second {
if len(stream.header) == 0 {
time.Sleep(time.Millisecond * 100)
continue
}
break
}
if len(stream.header) == 0 {
t.Errorf("Expecting header to be set")
}
// fmt.Println(stream.buffer.q.Get(12))
//TODO: Test what happens when the buffer is full (should evict everything before the last keyframe)
+13 -4
View File
@@ -13,14 +13,21 @@ type VideoStream interface {
String() string
}
type HLSVideoManifest interface {
type VideoManifest interface {
GetManifestID() string
GetVideoFormat() VideoFormat
String() string
}
type HLSVideoManifest interface {
VideoManifest
GetManifest() (*m3u8.MasterPlaylist, error)
GetVideoStream(strmID string) (HLSVideoStream, error)
AddVideoStream(strmID string, variant *m3u8.Variant) error
// AddVideoStream(strmID string, variant *m3u8.Variant) (HLSVideoStream, error)
AddVideoStream(strm HLSVideoStream, variant *m3u8.Variant) error
GetStreamVariant(strmID string) (*m3u8.Variant, error)
GetVideoStreams() []HLSVideoStream
DeleteVideoStream(strmID string) error
String() string
}
//HLSVideoStream contains the master playlist, media playlists in it, and the segments in them. Each media playlist also has a streamID.
@@ -28,7 +35,7 @@ type HLSVideoManifest interface {
type HLSVideoStream interface {
VideoStream
GetStreamPlaylist() (*m3u8.MediaPlaylist, error)
GetStreamVariant() *m3u8.Variant
// GetStreamVariant() *m3u8.Variant
GetHLSSegment(segName string) (*HLSSegment, error)
AddHLSSegment(seg *HLSSegment) error
SetSubscriber(f func(seg *HLSSegment, eof bool))
@@ -39,4 +46,6 @@ type RTMPVideoStream interface {
VideoStream
ReadRTMPFromStream(ctx context.Context, dst av.MuxCloser) error
WriteRTMPToStream(ctx context.Context, src av.DemuxCloser) error
Height() int
Width() int
}