Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Store alternatives in all variants #158

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 51 additions & 9 deletions reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ func (p *MasterPlaylist) decode(buf *bytes.Buffer, strict bool) error {
var eof bool

state := new(decodingState)
state.alternatives = make(map[string][]*Alternative)

for !eof {
line, err := buf.ReadString('\n')
Expand All @@ -81,9 +82,56 @@ func (p *MasterPlaylist) decode(buf *bytes.Buffer, strict bool) error {
if strict && !state.m3u {
return errors.New("#EXTM3U absent")
}

p.setAlternatives(state)

return nil
}

// Set alternatives for variants in master playlist. Internal function.
func (p *MasterPlaylist) setAlternatives(state *decodingState) {
for _, variant := range p.Variants {
alts := []*Alternative{}

if variant.Audio != "" {
toSearch := state.alternatives["AUDIO"]
for _, alt := range toSearch {
if variant.Audio == alt.GroupId {
alts = append(alts, alt)
}
}
}
if variant.Video != "" {
toSearch := state.alternatives["VIDEO"]
for _, alt := range toSearch {
if variant.Video == alt.GroupId {
alts = append(alts, alt)
}
}
}
if variant.Subtitles != "" {
toSearch := state.alternatives["SUBTITLES"]
for _, alt := range toSearch {
if variant.Subtitles == alt.GroupId {
alts = append(alts, alt)
}
}
}
if variant.Captions != "" {
toSearch := state.alternatives["CLOSED-CAPTIONS"]
for _, alt := range toSearch {
if variant.Captions == alt.GroupId {
alts = append(alts, alt)
}
}
}

if len(alts) != 0 {
variant.Alternatives = alts
}
}
}

// Decode parses a media playlist passed from the buffer. If `strict`
// parameter is true then return first syntax error.
func (p *MediaPlaylist) Decode(data bytes.Buffer, strict bool) error {
Expand Down Expand Up @@ -190,6 +238,7 @@ func decode(buf *bytes.Buffer, strict bool, customDecoders []CustomDecoder) (Pla
var err error

state := new(decodingState)
state.alternatives = make(map[string][]*Alternative) // create the map for alternatives
wv := new(WV)

master = NewMasterPlaylist()
Expand Down Expand Up @@ -240,6 +289,7 @@ func decode(buf *bytes.Buffer, strict bool, customDecoders []CustomDecoder) (Pla

switch state.listType {
case MASTER:
master.setAlternatives(state)
return master, MASTER, nil
case MEDIA:
if media.Closed || media.MediaType == EVENT {
Expand Down Expand Up @@ -331,15 +381,11 @@ func decodeLineOfMasterPlaylist(p *MasterPlaylist, state *decodingState, line st
alt.URI = v
}
}
state.alternatives = append(state.alternatives, &alt)
state.alternatives[alt.Type] = append(state.alternatives[alt.Type], &alt)
case !state.tagStreamInf && strings.HasPrefix(line, "#EXT-X-STREAM-INF:"):
state.tagStreamInf = true
state.listType = MASTER
state.variant = new(Variant)
if len(state.alternatives) > 0 {
state.variant.Alternatives = state.alternatives
state.alternatives = nil
}
p.Variants = append(p.Variants, state.variant)
for k, v := range decodeParamsLine(line[18:]) {
switch k {
Expand Down Expand Up @@ -395,10 +441,6 @@ func decodeLineOfMasterPlaylist(p *MasterPlaylist, state *decodingState, line st
state.listType = MASTER
state.variant = new(Variant)
state.variant.Iframe = true
if len(state.alternatives) > 0 {
state.variant.Alternatives = state.alternatives
state.alternatives = nil
}
p.Variants = append(p.Variants, state.variant)
for k, v := range decodeParamsLine(line[26:]) {
switch k {
Expand Down
44 changes: 44 additions & 0 deletions reader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,50 @@ func TestDecodeMasterPlaylistWithAlternatives(t *testing.T) {
// fmt.Println(p.Encode().String())
}

func TestDecodeMasterPlaylistWithAlternativesDifferentOrder(t *testing.T) {
f, err := os.Open("sample-playlists/master-with-alternatives-diff-order.m3u8")
if err != nil {
t.Fatal(err)
}
p := NewMasterPlaylist()
err = p.DecodeFrom(bufio.NewReader(f), false)
if err != nil {
t.Fatal(err)
}
// check parsed values
if p.ver != 3 {
t.Errorf("Version of parsed playlist = %d (must = 3)", p.ver)
}
if len(p.Variants) != 7 {
t.Fatal("not all variants in master playlist parsed")
}

for i, v := range p.Variants {
if i == 0 && len(v.Alternatives) != 3 {
t.Errorf("Expect 3 alternatives at %d but got %d\n", i, len(v.Alternatives))
}
if i == 1 && len(v.Alternatives) != 4 {
t.Errorf("Expect 4 alternatives at %d but got %d\n", i, len(v.Alternatives))
}
if i == 2 && len(v.Alternatives) != 4 {
t.Errorf("Expect 4 alternatives at %d but got %d\n", i, len(v.Alternatives))
}
if i == 3 && len(v.Alternatives) != 1 {
t.Errorf("Expect 1 alternative at %d but got %d\n", i, len(v.Alternatives))
}
if i == 4 && len(v.Alternatives) != 0 {
t.Errorf("Expect 0 alternatives at %d but got %d\n", i, len(v.Alternatives))
}
if i == 5 && len(v.Alternatives) != 1 {
t.Errorf("Expect 1 alternative at %d but got %d\n", i, len(v.Alternatives))
}
if i == 6 && len(v.Alternatives) != 1 {
t.Errorf("Expect 1 alternative at %d but got %d\n", i, len(v.Alternatives))
}
}

}

func TestDecodeMasterPlaylistWithClosedCaptionEqNone(t *testing.T) {
f, err := os.Open("sample-playlists/master-with-closed-captions-eq-none.m3u8")
if err != nil {
Expand Down
54 changes: 54 additions & 0 deletions sample-playlists/master-with-alternatives-diff-order.m3u8
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#EXTM3U

# Videos
#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="low",NAME="Main",DEFAULT=YES,URI="low/main/audio-video.m3u8"
#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="low",NAME="Centerfield",DEFAULT=NO,URI="low/centerfield/audio-video.m3u8"
#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="low",NAME="Dugout",DEFAULT=NO,URI="low/dugout/audio-video.m3u8"
#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="mid",NAME="Main",DEFAULT=YES,URI="mid/main/audio-video.m3u8"
#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="mid",NAME="Centerfield",DEFAULT=NO,URI="mid/centerfield/audio-video.m3u8"
#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="mid",NAME="Dugout",DEFAULT=NO,URI="mid/dugout/audio-video.m3u8"
#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="hi",NAME="Main",DEFAULT=YES,URI="hi/main/audio-video.m3u8"
#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="hi",NAME="Centerfield",DEFAULT=NO,URI="hi/centerfield/audio-video.m3u8"
#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="hi",NAME="Dugout",DEFAULT=NO,URI="hi/dugout/audio-video.m3u8"

# Audios
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="a1",NAME="Audio1",DEFAULT=YES,URI="audio/a1.m3u8"

# Captions
#EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS,GROUP-ID="cc",LANGUAGE="en",NAME="English",DEFAULT=YES,AUTOSELECT=YES

# Subtitles
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",LANGUAGE="en",NAME="English",AUTOSELECT=YES,DEFAULT=YES,FORCED=NO,URI="s1/en/prog_index.m3u8"

# External Media with no usage
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub2",LANGUAGE="en",NAME="English",AUTOSELECT=YES,DEFAULT=YES,FORCED=NO,URI="s1/en/prog_index.m3u8"

# Video
#EXT-X-STREAM-INF:BANDWIDTH=1280000,VIDEO="low"
low/main/audio-video.m3u8

# Video and CC
#EXT-X-STREAM-INF:BANDWIDTH=2560000,VIDEO="mid",CLOSED-CAPTIONS="cc"
mid/main/audio-video.m3u8

# Video and Subtitles
#EXT-X-STREAM-INF:BANDWIDTH=7680000,VIDEO="hi",SUBTITLES="sub1"
hi/main/audio-video.m3u8

# Audio
#EXT-X-STREAM-INF:BANDWIDTH=65000,CODECS="mp4a.40.5",AUDIO="a1"
main/audio-only.m3u8

# Non-existent group-id
#EXT-X-STREAM-INF:BANDWIDTH=2560000,VIDEO="missing"
missing-id/main/audio-video.m3u8

# One group-id present and one non-existent group id
#EXT-X-STREAM-INF:BANDWIDTH=2560000,VIDEO="missing",AUDIO="a1"
missing-and-valid-id/main/audio-video.m3u8

# Stream coming before external media
#EXT-X-STREAM-INF:BANDWIDTH=2560000,VIDEO="v1"
pre-media/main/audio-video.m3u8

#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="v1",NAME="AfterStream",DEFAULT=NO,URI="after-stream/audio-video.m3u8"
2 changes: 1 addition & 1 deletion structure.go
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ type decodingState struct {
duration float64
title string
variant *Variant
alternatives []*Alternative
alternatives map[string][]*Alternative
xkey *Key
xmap *Map
scte *SCTE
Expand Down