diff --git a/reader.go b/reader.go index b19324eb..55e9bb67 100644 --- a/reader.go +++ b/reader.go @@ -329,6 +329,12 @@ func decodeLineOfMasterPlaylist(p *MasterPlaylist, state *decodingState, line st alt.Subtitles = v case "URI": alt.URI = v + case "CHANNELS": + channels, err := strconv.Atoi(v) + if err != nil { + return fmt.Errorf("non-integer value %q for CHANNELS attribute", v) + } + alt.Channels = uint(channels) } } state.alternatives = append(state.alternatives, &alt) diff --git a/reader_test.go b/reader_test.go index 8d60b16c..8bfed863 100644 --- a/reader_test.go +++ b/reader_test.go @@ -972,6 +972,40 @@ func TestDecodeMediaPlaylistStartTime(t *testing.T) { } } +func TestDecodeMasterChannels(t *testing.T) { + f, err := os.Open("sample-playlists/master-with-channels.m3u8") + if err != nil { + t.Fatal(err) + } + p, listType, err := DecodeFrom(bufio.NewReader(f), true) + if err != nil { + t.Fatal(err) + } + + if listType != MASTER { + t.Error("Input not recognized as master playlist.") + } + pp := p.(*MasterPlaylist) + + alt0 := pp.Variants[0].Alternatives[0] + if alt0.Type != "AUDIO" { + t.Error("Expected AUDIO track in test input Alternatives[0]") + } + + if alt0.Channels != 2 { + t.Error("Expected 2 channels track in test input Alternatives[0]") + } + + alt1 := pp.Variants[0].Alternatives[1] + if alt1.Type != "AUDIO" { + t.Error("Expected AUDIO track in test input Alternatives[1]") + } + + if alt1.Channels != 6 { + t.Error("Expected 6 channels track in test input Alternatives[1]") + } +} + /**************** * Benchmarks * ****************/ diff --git a/sample-playlists/master-with-channels.m3u8 b/sample-playlists/master-with-channels.m3u8 new file mode 100644 index 00000000..5045a3e6 --- /dev/null +++ b/sample-playlists/master-with-channels.m3u8 @@ -0,0 +1,13 @@ +#EXTM3U +#EXT-X-VERSION:6 +#EXT-X-INDEPENDENT-SEGMENTS + + +#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="a1",NAME="English",LANGUAGE="en-US",AUTOSELECT=YES,DEFAULT=YES,CHANNELS="2",URI="a1/prog_index.m3u8" +#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="a2",NAME="English",LANGUAGE="en-US",AUTOSELECT=YES,DEFAULT=YES,CHANNELS="6",URI="a2/prog_index.m3u8" + +#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=2190673,BANDWIDTH=2523597,CODECS="avc1.640020,mp4a.40.2",RESOLUTION=960x540,FRAME-RATE=60.000,AUDIO="a1" +v5/prog_index.m3u8 +#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=8052613,BANDWIDTH=9873268,CODECS="avc1.64002a,mp4a.40.2",RESOLUTION=1920x1080,FRAME-RATE=60.000,AUDIO="a1" +v9/prog_index.m3u8 + diff --git a/structure.go b/structure.go index eb5d01a2..dd1ee2ac 100644 --- a/structure.go +++ b/structure.go @@ -197,6 +197,7 @@ type Alternative struct { Forced string Characteristics string Subtitles string + Channels uint } // MediaSegment structure represents a media segment included in a diff --git a/writer.go b/writer.go index dec3690c..96c08636 100644 --- a/writer.go +++ b/writer.go @@ -155,6 +155,11 @@ func (p *MasterPlaylist) Encode() *bytes.Buffer { p.buf.WriteString(alt.URI) p.buf.WriteRune('"') } + if alt.Channels != 0 { + p.buf.WriteString(",CHANNELS=\"") + p.buf.WriteString(strconv.FormatUint(uint64(alt.Channels), 10)) + p.buf.WriteRune('"') + } p.buf.WriteRune('\n') } }