diff --git a/reader.go b/reader.go index 8e4ff03a..97cc81dd 100644 --- a/reader.go +++ b/reader.go @@ -331,6 +331,8 @@ func decodeLineOfMasterPlaylist(p *MasterPlaylist, state *decodingState, line st alt.URI = v case "INSTREAM-ID": alt.InstreamId = v + case "CHANNELS": + alt.Channels = v } } state.alternatives = append(state.alternatives, &alt) diff --git a/reader_test.go b/reader_test.go index 5ac13130..6952d525 100644 --- a/reader_test.go +++ b/reader_test.go @@ -234,6 +234,24 @@ func TestDecodeMasterPlaylistWithIndependentSegments(t *testing.T) { } } +func TestDecodeMasterPlaylistInstreamIdAndChannels(t *testing.T) { + f, err := os.Open("sample-playlists/master-with-instream-id-and-channels.m3u8") + if err != nil { + t.Fatal(err) + } + p := NewMasterPlaylist() + err = p.DecodeFrom(bufio.NewReader(f), false) + if err != nil { + t.Fatal(err) + } + if p.Variants[0].Alternatives[0].Channels != "2" { + t.Error("Expected CHANNELS to be 2") + } + if p.Variants[0].Alternatives[1].InstreamId != "CC1" { + t.Error("Expected INSTREAM-ID to be CC1") + } +} + func TestDecodeMasterWithHLSV7(t *testing.T) { f, err := os.Open("sample-playlists/master-with-hlsv7.m3u8") if err != nil { diff --git a/sample-playlists/master-with-instream-id-and-channels.m3u8 b/sample-playlists/master-with-instream-id-and-channels.m3u8 new file mode 100644 index 00000000..7977e6f5 --- /dev/null +++ b/sample-playlists/master-with-instream-id-and-channels.m3u8 @@ -0,0 +1,8 @@ +#EXTM3U +#EXT-X-VERSION:6 +#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio-aacl-96",LANGUAGE="en",NAME="English",DEFAULT=YES,AUTOSELECT=YES,CHANNELS="2" +#EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS,GROUP-ID="textstream",NAME="textstream",DEFAULT=YES,AUTOSELECT=YES,INSTREAM-ID="CC1" +#EXT-X-STREAM-INF:BANDWIDTH=812000,AVERAGE-BANDWIDTH=738000,CODECS="mp4a.40.2,avc1.640015",RESOLUTION=480x270,FRAME-RATE=30,VIDEO-RANGE=SDR,AUDIO="audio-aacl-96",CLOSED-CAPTIONS="textstream" +0.m3u8 +#EXT-X-STREAM-INF:BANDWIDTH=1104000,AVERAGE-BANDWIDTH=1003000,CODECS="mp4a.40.2,avc1.64001E",RESOLUTION=640x360,FRAME-RATE=30,VIDEO-RANGE=SDR,AUDIO="audio-aacl-96",CLOSED-CAPTIONS="textstream" +1.m3u8 diff --git a/structure.go b/structure.go index 7d9546c8..9fe41a26 100644 --- a/structure.go +++ b/structure.go @@ -198,6 +198,7 @@ type Alternative struct { Characteristics string Subtitles string InstreamId string + Channels string } // MediaSegment structure represents a media segment included in a diff --git a/writer.go b/writer.go index ce43095b..b64c8cf9 100644 --- a/writer.go +++ b/writer.go @@ -160,6 +160,11 @@ func (p *MasterPlaylist) Encode() *bytes.Buffer { p.buf.WriteString(alt.InstreamId) p.buf.WriteRune('"') } + if alt.Channels != "" { + p.buf.WriteString(",CHANNELS=\"") + p.buf.WriteString(alt.Channels) + p.buf.WriteRune('"') + } p.buf.WriteRune('\n') } } diff --git a/writer_test.go b/writer_test.go index fa2544fb..7b74c383 100644 --- a/writer_test.go +++ b/writer_test.go @@ -833,6 +833,33 @@ func TestNewMasterPlaylistWithClosedCaptionEqNone(t *testing.T) { } } +func TestEncodeMasterPlaylistInstreamIdAndChannels(t *testing.T) { + f, err := os.Open("sample-playlists/master-with-instream-id-and-channels.m3u8") + if err != nil { + t.Fatal(err) + } + m := NewMasterPlaylist() + err = m.DecodeFrom(bufio.NewReader(f), false) + if err != nil { + t.Fatal(err) + } + + m.Variants[0].Alternatives[0].Channels = "1" + m.Variants[0].Alternatives[1].InstreamId = "CC2" + + result := m.String() + + expected := `CHANNELS="1"` + if !strings.Contains(m.String(), expected) { + t.Fatalf("Master playlist did not contain: %s\nMaster Playlist:\n%v", expected, result) + } + + expected = `INSTREAM-ID="CC2"` + if !strings.Contains(m.String(), expected) { + t.Fatalf("Master playlist did not contain: %s\nMaster Playlist:\n%v", expected, result) + } +} + // Create new master playlist with params // Add media playlist func TestNewMasterPlaylistWithParams(t *testing.T) {