diff --git a/reader.go b/reader.go index b19324eb..e51e4dea 100644 --- a/reader.go +++ b/reader.go @@ -81,9 +81,31 @@ func (p *MasterPlaylist) decode(buf *bytes.Buffer, strict bool) error { if strict && !state.m3u { return errors.New("#EXTM3U absent") } + if len(state.alternatives) > 0 { + p.setVariantAlternatives(state.alternatives) + } + return nil } +func (p *MasterPlaylist) setVariantAlternatives(alternatives map[string][]*Alternative) { + for _, variant := range p.Variants { + // enumerate ext-x-media types + if val, ok := alternatives["AUDIO"+variant.Audio]; ok { + variant.Alternatives = append(variant.Alternatives, val...) + } + if val, ok := alternatives["VIDEO"+variant.Video]; ok { + variant.Alternatives = append(variant.Alternatives, val...) + } + if val, ok := alternatives["SUBTITLES"+variant.Subtitles]; ok { + variant.Alternatives = append(variant.Alternatives, val...) + } + if val, ok := alternatives["CLOSED-CAPTIONS"+variant.Captions]; ok { + variant.Alternatives = append(variant.Alternatives, val...) + } + } +} + // 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 { @@ -238,6 +260,10 @@ func decode(buf *bytes.Buffer, strict bool, customDecoders []CustomDecoder) (Pla return nil, listType, errors.New("#EXTM3U absent") } + if state.listType == MASTER && len(state.alternatives) > 0 { + master.setVariantAlternatives(state.alternatives) + } + switch state.listType { case MASTER: return master, MASTER, nil @@ -301,6 +327,9 @@ func decodeLineOfMasterPlaylist(p *MasterPlaylist, state *decodingState, line st case strings.HasPrefix(line, "#EXT-X-MEDIA:"): var alt Alternative state.listType = MASTER + if state.alternatives == nil { + state.alternatives = make(map[string][]*Alternative) + } for k, v := range decodeParamsLine(line[13:]) { switch k { case "TYPE": @@ -331,15 +360,12 @@ func decodeLineOfMasterPlaylist(p *MasterPlaylist, state *decodingState, line st alt.URI = v } } - state.alternatives = append(state.alternatives, &alt) + // create hash out of type + group id + state.alternatives[alt.Type+alt.GroupId] = append(state.alternatives[alt.Type+alt.GroupId], &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 { @@ -395,10 +421,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 { diff --git a/reader_test.go b/reader_test.go index 8d60b16c..7e21e40e 100644 --- a/reader_test.go +++ b/reader_test.go @@ -86,17 +86,17 @@ func TestDecodeMasterPlaylistWithAlternatives(t *testing.T) { } // TODO check other values for i, v := range p.Variants { - if i == 0 && len(v.Alternatives) != 3 { - t.Fatalf("not all alternatives from #EXT-X-MEDIA parsed (has %d but should be 3", len(v.Alternatives)) + if i == 0 && len(v.Alternatives) != 6 { + t.Fatalf("not all alternatives from #EXT-X-MEDIA parsed (has %d but should be 6", len(v.Alternatives)) } - if i == 1 && len(v.Alternatives) != 3 { - t.Fatalf("not all alternatives from #EXT-X-MEDIA parsed (has %d but should be 3", len(v.Alternatives)) + if i == 1 && len(v.Alternatives) != 6 { + t.Fatalf("not all alternatives from #EXT-X-MEDIA parsed (has %d but should be 6", len(v.Alternatives)) } - if i == 2 && len(v.Alternatives) != 3 { - t.Fatalf("not all alternatives from #EXT-X-MEDIA parsed (has %d but should be 3", len(v.Alternatives)) + if i == 2 && len(v.Alternatives) != 6 { + t.Fatalf("not all alternatives from #EXT-X-MEDIA parsed (has %d but should be 6", len(v.Alternatives)) } - if i == 3 && len(v.Alternatives) > 0 { - t.Fatal("should not be alternatives for this variant") + if i == 3 && len(v.Alternatives) != 3 { + t.Fatalf("not all alternatives from #EXT-X-MEDIA parsed (has %d but should be 3", len(v.Alternatives)) } } // fmt.Println(p.Encode().String()) diff --git a/sample-playlists/master-with-alternatives.m3u8 b/sample-playlists/master-with-alternatives.m3u8 index ea6ea2f6..47b890f5 100644 --- a/sample-playlists/master-with-alternatives.m3u8 +++ b/sample-playlists/master-with-alternatives.m3u8 @@ -2,17 +2,22 @@ #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-STREAM-INF:BANDWIDTH=1280000,VIDEO="low" -low/main/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-STREAM-INF:BANDWIDTH=2560000,VIDEO="mid" -mid/main/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" -#EXT-X-STREAM-INF:BANDWIDTH=7680000,VIDEO="hi" + +#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio",NAME="English",LANGUAGE="en",DEFAULT=YES,AUTOSELECT=YES +#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio",NAME="Spanish",LANGUAGE="es",DEFAULT=NO,AUTOSELECT=YES,URI="main/spa.m3u8" +#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio",NAME="French",LANGUAGE="fr",DEFAULT=NO,AUTOSELECT=YES,URI="main/fra.m3u8" + +#EXT-X-STREAM-INF:BANDWIDTH=1280000,VIDEO="low",AUDIO="audio" +low/main/audio-video.m3u8 +#EXT-X-STREAM-INF:BANDWIDTH=2560000,VIDEO="mid",AUDIO="audio" +mid/main/audio-video.m3u8 +#EXT-X-STREAM-INF:BANDWIDTH=7680000,VIDEO="hi",AUDIO="audio" hi/main/audio-video.m3u8 -#EXT-X-STREAM-INF:BANDWIDTH=65000,CODECS="mp4a.40.5" +#EXT-X-STREAM-INF:BANDWIDTH=65000,CODECS="mp4a.40.5",AUDIO="audio" main/audio-only.m3u8 diff --git a/structure.go b/structure.go index eb5d01a2..b05ef2e4 100644 --- a/structure.go +++ b/structure.go @@ -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