From b7225022b722285067c00eeb61f4a7cfff058c6a Mon Sep 17 00:00:00 2001 From: Mark Niebur Date: Thu, 6 Jan 2022 14:37:46 -0700 Subject: [PATCH 1/2] Append all matching alternatives to variant --- reader.go | 50 +++++++++++++++---- reader_test.go | 16 +++--- .../master-with-alternatives.m3u8 | 17 ++++--- structure.go | 2 +- 4 files changed, 61 insertions(+), 24 deletions(-) diff --git a/reader.go b/reader.go index b19324eb..bb39b763 100644 --- a/reader.go +++ b/reader.go @@ -81,6 +81,24 @@ func (p *MasterPlaylist) decode(buf *bytes.Buffer, strict bool) error { if strict && !state.m3u { return errors.New("#EXTM3U absent") } + if len(state.alternatives) > 0 { + for _, variant := range p.Variants { + // enumerate ext-x-media types + if val, ok := state.alternatives["AUDIO"+variant.Audio]; ok { + variant.Alternatives = append(variant.Alternatives, val...) + } + if val, ok := state.alternatives["VIDEO"+variant.Video]; ok { + variant.Alternatives = append(variant.Alternatives, val...) + } + if val, ok := state.alternatives["SUBTITLES"+variant.Subtitles]; ok { + variant.Alternatives = append(variant.Alternatives, val...) + } + if val, ok := state.alternatives["CLOSED-CAPTIONS"+variant.Captions]; ok { + variant.Alternatives = append(variant.Alternatives, val...) + } + } + } + return nil } @@ -238,6 +256,24 @@ 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 { + for _, variant := range master.Variants { + // enumerate ext-x-media types + if val, ok := state.alternatives["AUDIO"+variant.Audio]; ok { + variant.Alternatives = append(variant.Alternatives, val...) + } + if val, ok := state.alternatives["VIDEO"+variant.Video]; ok { + variant.Alternatives = append(variant.Alternatives, val...) + } + if val, ok := state.alternatives["SUBTITLES"+variant.Subtitles]; ok { + variant.Alternatives = append(variant.Alternatives, val...) + } + if val, ok := state.alternatives["CLOSED-CAPTIONS"+variant.Captions]; ok { + variant.Alternatives = append(variant.Alternatives, val...) + } + } + } + switch state.listType { case MASTER: return master, MASTER, nil @@ -301,6 +337,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 +370,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 +431,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 From 6b9d86c48fe0ee1761b3efcdceec0f8a714dfd7a Mon Sep 17 00:00:00 2001 From: Mark Niebur Date: Thu, 6 Jan 2022 14:59:54 -0700 Subject: [PATCH 2/2] refactored alternative setting to function --- reader.go | 50 ++++++++++++++++++++------------------------------ 1 file changed, 20 insertions(+), 30 deletions(-) diff --git a/reader.go b/reader.go index bb39b763..e51e4dea 100644 --- a/reader.go +++ b/reader.go @@ -82,26 +82,30 @@ func (p *MasterPlaylist) decode(buf *bytes.Buffer, strict bool) error { return errors.New("#EXTM3U absent") } if len(state.alternatives) > 0 { - for _, variant := range p.Variants { - // enumerate ext-x-media types - if val, ok := state.alternatives["AUDIO"+variant.Audio]; ok { - variant.Alternatives = append(variant.Alternatives, val...) - } - if val, ok := state.alternatives["VIDEO"+variant.Video]; ok { - variant.Alternatives = append(variant.Alternatives, val...) - } - if val, ok := state.alternatives["SUBTITLES"+variant.Subtitles]; ok { - variant.Alternatives = append(variant.Alternatives, val...) - } - if val, ok := state.alternatives["CLOSED-CAPTIONS"+variant.Captions]; ok { - variant.Alternatives = append(variant.Alternatives, val...) - } - } + 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 { @@ -257,21 +261,7 @@ func decode(buf *bytes.Buffer, strict bool, customDecoders []CustomDecoder) (Pla } if state.listType == MASTER && len(state.alternatives) > 0 { - for _, variant := range master.Variants { - // enumerate ext-x-media types - if val, ok := state.alternatives["AUDIO"+variant.Audio]; ok { - variant.Alternatives = append(variant.Alternatives, val...) - } - if val, ok := state.alternatives["VIDEO"+variant.Video]; ok { - variant.Alternatives = append(variant.Alternatives, val...) - } - if val, ok := state.alternatives["SUBTITLES"+variant.Subtitles]; ok { - variant.Alternatives = append(variant.Alternatives, val...) - } - if val, ok := state.alternatives["CLOSED-CAPTIONS"+variant.Captions]; ok { - variant.Alternatives = append(variant.Alternatives, val...) - } - } + master.setVariantAlternatives(state.alternatives) } switch state.listType {