From 9a75003efa82536f4fe502c0d913e29121de1523 Mon Sep 17 00:00:00 2001 From: Andrew Sinclair Date: Mon, 30 Apr 2018 14:03:10 +1000 Subject: [PATCH 1/7] Add initial params --- structure.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/structure.go b/structure.go index b442cdec..256ee84d 100644 --- a/structure.go +++ b/structure.go @@ -168,6 +168,8 @@ type VariantParams struct { Name string // EXT-X-STREAM-INF only (non standard Wowza/JWPlayer extension to name the variant/quality in UA) Iframe bool // EXT-X-I-FRAME-STREAM-INF Alternatives []*Alternative // EXT-X-MEDIA + VideoRange string + HCDPLevel string } // This structure represents EXT-X-MEDIA tag in variants. From 9d9dcb6a66faf47e883a981d4152620ffbf60368 Mon Sep 17 00:00:00 2001 From: Andrew Sinclair Date: Mon, 30 Apr 2018 16:56:35 +1000 Subject: [PATCH 2/7] Add new variant parameters and write --- structure.go | 28 +++++++++++++++------------- writer.go | 20 ++++++++++++++++++++ 2 files changed, 35 insertions(+), 13 deletions(-) diff --git a/structure.go b/structure.go index 256ee84d..0f1c7fcf 100644 --- a/structure.go +++ b/structure.go @@ -157,19 +157,21 @@ type Variant struct { // This structure represents additional parameters for a variant // used in EXT-X-STREAM-INF and EXT-X-I-FRAME-STREAM-INF type VariantParams struct { - ProgramId uint32 - Bandwidth uint32 - Codecs string - Resolution string - Audio string // EXT-X-STREAM-INF only - Video string - Subtitles string // EXT-X-STREAM-INF only - Captions string // EXT-X-STREAM-INF only - Name string // EXT-X-STREAM-INF only (non standard Wowza/JWPlayer extension to name the variant/quality in UA) - Iframe bool // EXT-X-I-FRAME-STREAM-INF - Alternatives []*Alternative // EXT-X-MEDIA - VideoRange string - HCDPLevel string + ProgramId uint32 + Bandwidth uint32 + Codecs string + Resolution string + Audio string // EXT-X-STREAM-INF only + Video string + Subtitles string // EXT-X-STREAM-INF only + Captions string // EXT-X-STREAM-INF only + Name string // EXT-X-STREAM-INF only (non standard Wowza/JWPlayer extension to name the variant/quality in UA) + Iframe bool // EXT-X-I-FRAME-STREAM-INF + Alternatives []*Alternative // EXT-X-MEDIA + VideoRange string + HCDPLevel string + FrameRate float64 + AverageBandwidth uint32 } // This structure represents EXT-X-MEDIA tag in variants. diff --git a/writer.go b/writer.go index 36f0c8ec..ef580886 100644 --- a/writer.go +++ b/writer.go @@ -174,6 +174,11 @@ func (p *MasterPlaylist) Encode() *bytes.Buffer { p.buf.WriteString(strconv.FormatUint(uint64(pl.ProgramId), 10)) p.buf.WriteString(",BANDWIDTH=") p.buf.WriteString(strconv.FormatUint(uint64(pl.Bandwidth), 10)) + if pl.AverageBandwidth != uint32(0) { + p.buf.WriteString(",AVERAGE-BANDWIDTH=\"") + p.buf.WriteString(strconv.FormatUint(uint64(pl.AverageBandwidth), 10)) + p.buf.WriteRune('"') + } if pl.Codecs != "" { p.buf.WriteString(",CODECS=\"") p.buf.WriteString(pl.Codecs) @@ -213,6 +218,21 @@ func (p *MasterPlaylist) Encode() *bytes.Buffer { p.buf.WriteString(pl.Name) p.buf.WriteRune('"') } + if pl.FrameRate != 0.0 { + p.buf.WriteString(",FRAME-RATE=\"") + p.buf.WriteString(strconv.FormatFloat(pl.FrameRate, 'f', 3, 32)) + p.buf.WriteRune('"') + } + if pl.VideoRange != "" { + p.buf.WriteString(",VIDEO-RANGE=\"") + p.buf.WriteString(pl.VideoRange) + p.buf.WriteRune('"') + } + if pl.HCDPLevel != "" { + p.buf.WriteString(",VIDEO-RANGE=\"") + p.buf.WriteString(pl.HCDPLevel) + p.buf.WriteRune('"') + } p.buf.WriteRune('\n') p.buf.WriteString(pl.URI) if p.Args != "" { From c84c2b39bb8490974c7f663c2c944f94fd14dc45 Mon Sep 17 00:00:00 2001 From: Andrew Sinclair Date: Wed, 9 May 2018 13:01:16 +1000 Subject: [PATCH 3/7] Update value --- writer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/writer.go b/writer.go index ef580886..ede73bb8 100644 --- a/writer.go +++ b/writer.go @@ -229,7 +229,7 @@ func (p *MasterPlaylist) Encode() *bytes.Buffer { p.buf.WriteRune('"') } if pl.HCDPLevel != "" { - p.buf.WriteString(",VIDEO-RANGE=\"") + p.buf.WriteString(",HDCP-LEVEL=\"") p.buf.WriteString(pl.HCDPLevel) p.buf.WriteRune('"') } From 1d3a4426dde128b6c0ef0ca41197a7e07ebae6e1 Mon Sep 17 00:00:00 2001 From: Andrew Sinclair Date: Wed, 23 May 2018 12:24:11 +1000 Subject: [PATCH 4/7] Add a test --- writer_test.go | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/writer_test.go b/writer_test.go index c737e690..a2b0129e 100644 --- a/writer_test.go +++ b/writer_test.go @@ -774,6 +774,42 @@ func TestMasterSetVersion(t *testing.T) { } } +// Create new master playlist supporting CLOSED-CAPTIONS=NONE +func TestNewMasterPlaylistWithV7Params(t *testing.T) { + m := NewMasterPlaylist() + + vp := &VariantParams{ + ProgramId: 0, + Bandwidth: 8000, + Codecs: "avc1", + Resolution: "1280x720", + Audio: "audio0", + Captions: "NONE", + VideoRange: "SDR", + FrameRate: 29.97, + HCDPLevel: "NONE", + } + + p, err := NewMediaPlaylist(1, 1) + if err != nil { + t.Fatalf("Create media playlist failed: %s", err) + } + m.Append(fmt.Sprintf("eng_rendition_rendition.m3u8"), p, *vp) + + expected := "CLOSED-CAPTIONS=NONE" + if !strings.Contains(m.String(), expected) { + t.Fatalf("Master playlist did not contain: %s\nMaster Playlist:\n%v", expected, m.String()) + } + // quotes need to be include if not eq NONE + vp.Captions = "CC1" + m2 := NewMasterPlaylist() + m2.Append(fmt.Sprintf("eng_rendition_rendition.m3u8"), p, *vp) + expected = `CLOSED-CAPTIONS="CC1"` + if !strings.Contains(m2.String(), expected) { + t.Fatalf("Master playlist did not contain: %s\nMaster Playlist:\n%v", expected, m2.String()) + } +} + /****************************** * Code generation examples * ******************************/ From bc5ec7d2ed53ef367eb0f1d8ce3efc1dcfb70c96 Mon Sep 17 00:00:00 2001 From: Bobby Peck Date: Fri, 19 Apr 2019 10:07:16 -0700 Subject: [PATCH 5/7] HLS V7 Tags reader & writer; with tests --- reader.go | 15 +++++++ reader_test.go | 58 ++++++++++++++++++++++++- sample-playlists/master-with-hlsv7.m3u8 | 32 ++++++++++++++ writer.go | 20 ++++++--- writer_test.go | 53 ++++++++-------------- 5 files changed, 135 insertions(+), 43 deletions(-) create mode 100644 sample-playlists/master-with-hlsv7.m3u8 diff --git a/reader.go b/reader.go index 14c1f899..9deee4f0 100644 --- a/reader.go +++ b/reader.go @@ -312,6 +312,10 @@ func decodeLineOfMasterPlaylist(p *MasterPlaylist, state *decodingState, line st if state.variant.FrameRate, err = strconv.ParseFloat(v, 64); strict && err != nil { return err } + case "VIDEO-RANGE": + state.variant.VideoRange = v + case "HDCP-LEVEL": + state.variant.HCDPLevel = v } } case state.tagStreamInf && !strings.HasPrefix(line, "#"): @@ -352,6 +356,17 @@ func decodeLineOfMasterPlaylist(p *MasterPlaylist, state *decodingState, line st state.variant.Audio = v case "VIDEO": state.variant.Video = v + case "AVERAGE-BANDWIDTH": + var val int + val, err = strconv.Atoi(v) + if strict && err != nil { + return err + } + state.variant.AverageBandwidth = uint32(val) + case "VIDEO-RANGE": + state.variant.VideoRange = v + case "HDCP-LEVEL": + state.variant.HCDPLevel = v } } case strings.HasPrefix(line, "#"): // unknown tags treated as comments diff --git a/reader_test.go b/reader_test.go index 4b491635..8b32a88b 100644 --- a/reader_test.go +++ b/reader_test.go @@ -233,6 +233,60 @@ func TestDecodeMasterPlaylistWithIndependentSegments(t *testing.T) { } } +func TestDecodeMasterWithHLSV7(t *testing.T) { + f, err := os.Open("sample-playlists/master-with-hlsv7.m3u8") + if err != nil { + t.Fatal(err) + } + p := NewMasterPlaylist() + err = p.DecodeFrom(bufio.NewReader(f), false) + if err != nil { + t.Fatal(err) + } + var unexpected []*Variant + expected := map[string]VariantParams{ + "sdr_720/prog_index.m3u8": {Bandwidth: 3971374, AverageBandwidth: 2778321, Codecs: "hvc1.2.4.L123.B0", Resolution: "1280x720", Captions: "NONE", VideoRange: "SDR", HCDPLevel: "NONE", FrameRate: 23.976}, + "sdr_1080/prog_index.m3u8": {Bandwidth: 10022043, AverageBandwidth: 6759875, Codecs: "hvc1.2.4.L123.B0", Resolution: "1920x1080", Captions: "NONE", VideoRange: "SDR", HCDPLevel: "TYPE-0", FrameRate: 23.976}, + "sdr_2160/prog_index.m3u8": {Bandwidth: 28058971, AverageBandwidth: 20985770, Codecs: "hvc1.2.4.L150.B0", Resolution: "3840x2160", Captions: "NONE", VideoRange: "SDR", HCDPLevel: "TYPE-1", FrameRate: 23.976}, + "dolby_720/prog_index.m3u8": {Bandwidth: 5327059, AverageBandwidth: 3385450, Codecs: "dvh1.05.01", Resolution: "1280x720", Captions: "NONE", VideoRange: "PQ", HCDPLevel: "NONE", FrameRate: 23.976}, + "dolby_1080/prog_index.m3u8": {Bandwidth: 12876596, AverageBandwidth: 7999361, Codecs: "dvh1.05.03", Resolution: "1920x1080", Captions: "NONE", VideoRange: "PQ", HCDPLevel: "TYPE-0", FrameRate: 23.976}, + "dolby_2160/prog_index.m3u8": {Bandwidth: 30041698, AverageBandwidth: 24975091, Codecs: "dvh1.05.06", Resolution: "3840x2160", Captions: "NONE", VideoRange: "PQ", HCDPLevel: "TYPE-1", FrameRate: 23.976}, + "hdr10_720/prog_index.m3u8": {Bandwidth: 5280654, AverageBandwidth: 3320040, Codecs: "hvc1.2.4.L123.B0", Resolution: "1280x720", Captions: "NONE", VideoRange: "PQ", HCDPLevel: "NONE", FrameRate: 23.976}, + "hdr10_1080/prog_index.m3u8": {Bandwidth: 12886714, AverageBandwidth: 7964551, Codecs: "hvc1.2.4.L123.B0", Resolution: "1920x1080", Captions: "NONE", VideoRange: "PQ", HCDPLevel: "TYPE-0", FrameRate: 23.976}, + "hdr10_2160/prog_index.m3u8": {Bandwidth: 29983769, AverageBandwidth: 24833402, Codecs: "hvc1.2.4.L150.B0", Resolution: "3840x2160", Captions: "NONE", VideoRange: "PQ", HCDPLevel: "TYPE-1", FrameRate: 23.976}, + "sdr_720/iframe_index.m3u8": {Bandwidth: 593626, AverageBandwidth: 248586, Codecs: "hvc1.2.4.L123.B0", Resolution: "1280x720", Iframe: true, VideoRange: "SDR", HCDPLevel: "NONE"}, + "sdr_1080/iframe_index.m3u8": {Bandwidth: 956552, AverageBandwidth: 399790, Codecs: "hvc1.2.4.L123.B0", Resolution: "1920x1080", Iframe: true, VideoRange: "SDR", HCDPLevel: "TYPE-0"}, + "sdr_2160/iframe_index.m3u8": {Bandwidth: 1941397, AverageBandwidth: 826971, Codecs: "hvc1.2.4.L150.B0", Resolution: "3840x2160", Iframe: true, VideoRange: "SDR", HCDPLevel: "TYPE-1"}, + "dolby_720/iframe_index.m3u8": {Bandwidth: 573073, AverageBandwidth: 232253, Codecs: "dvh1.05.01", Resolution: "1280x720", Iframe: true, VideoRange: "PQ", HCDPLevel: "NONE"}, + "dolby_1080/iframe_index.m3u8": {Bandwidth: 905037, AverageBandwidth: 365337, Codecs: "dvh1.05.03", Resolution: "1920x1080", Iframe: true, VideoRange: "PQ", HCDPLevel: "TYPE-0"}, + "dolby_2160/iframe_index.m3u8": {Bandwidth: 1893236, AverageBandwidth: 739114, Codecs: "dvh1.05.06", Resolution: "3840x2160", Iframe: true, VideoRange: "PQ", HCDPLevel: "TYPE-1"}, + "hdr10_720/iframe_index.m3u8": {Bandwidth: 572673, AverageBandwidth: 232511, Codecs: "hvc1.2.4.L123.B0", Resolution: "1280x720", Iframe: true, VideoRange: "PQ", HCDPLevel: "NONE"}, + "hdr10_1080/iframe_index.m3u8": {Bandwidth: 905053, AverageBandwidth: 364552, Codecs: "hvc1.2.4.L123.B0", Resolution: "1920x1080", Iframe: true, VideoRange: "PQ", HCDPLevel: "TYPE-0"}, + "hdr10_2160/iframe_index.m3u8": {Bandwidth: 1895477, AverageBandwidth: 739757, Codecs: "hvc1.2.4.L150.B0", Resolution: "3840x2160", Iframe: true, VideoRange: "PQ", HCDPLevel: "TYPE-1"}, + } + for _, variant := range p.Variants { + var found bool + for uri, vp := range expected { + if variant == nil || variant.URI != uri { + continue + } + if reflect.DeepEqual(variant.VariantParams, vp) { + delete(expected, uri) + found = true + } + } + if !found { + unexpected = append(unexpected, variant) + } + } + for uri, expect := range expected { + t.Errorf("not found: uri=%q %+v", uri, expect) + } + for _, unexpect := range unexpected { + t.Errorf("found but not expecting:%+v", unexpect) + } +} + /**************************** * Begin Test MediaPlaylist * ****************************/ @@ -435,7 +489,7 @@ func TestDecodeMediaPlaylistAutoDetectExtend(t *testing.T) { // Test for FullTimeParse of EXT-X-PROGRAM-DATE-TIME // We testing ISO/IEC 8601:2004 where we can get time in UTC, UTC with Nanoseconds // timeZone in formats '±00:00', '±0000', '±00' -// m3u8.FullTimeParse() +// FullTimeParse() func TestFullTimeParse(t *testing.T) { var timestamps = []struct { name string @@ -463,7 +517,7 @@ func TestFullTimeParse(t *testing.T) { // Test for StrictTimeParse of EXT-X-PROGRAM-DATE-TIME // We testing Strict format of RFC3339 where we can get time in UTC, UTC with Nanoseconds // timeZone in formats '±00:00', '±0000', '±00' -// m3u8.StrictTimeParse() +// StrictTimeParse() func TestStrictTimeParse(t *testing.T) { var timestamps = []struct { name string diff --git a/sample-playlists/master-with-hlsv7.m3u8 b/sample-playlists/master-with-hlsv7.m3u8 new file mode 100644 index 00000000..75ff36cd --- /dev/null +++ b/sample-playlists/master-with-hlsv7.m3u8 @@ -0,0 +1,32 @@ +# https://developer.apple.com/documentation/http_live_streaming/hls_authoring_specification_for_apple_devices/hls_authoring_specification_for_apple_devices_appendices +# +#EXTM3U +#EXT-X-VERSION:7 +#EXT-X-INDEPENDENT-SEGMENTS +#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=2778321,BANDWIDTH=3971374,VIDEO-RANGE=SDR,CODECS="hvc1.2.4.L123.B0",RESOLUTION=1280x720,FRAME-RATE=23.976,CLOSED-CAPTIONS=NONE,HDCP-LEVEL=NONE +sdr_720/prog_index.m3u8 +#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=6759875,BANDWIDTH=10022043,VIDEO-RANGE=SDR,CODECS="hvc1.2.4.L123.B0",RESOLUTION=1920x1080,FRAME-RATE=23.976,CLOSED-CAPTIONS=NONE,HDCP-LEVEL=TYPE-0 +sdr_1080/prog_index.m3u8 +#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=20985770,BANDWIDTH=28058971,VIDEO-RANGE=SDR,CODECS="hvc1.2.4.L150.B0",RESOLUTION=3840x2160,FRAME-RATE=23.976,CLOSED-CAPTIONS=NONE,HDCP-LEVEL=TYPE-1 +sdr_2160/prog_index.m3u8 +#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=3385450,BANDWIDTH=5327059,VIDEO-RANGE=PQ,CODECS="dvh1.05.01",RESOLUTION=1280x720,FRAME-RATE=23.976,CLOSED-CAPTIONS=NONE,HDCP-LEVEL=NONE +dolby_720/prog_index.m3u8 +#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=7999361,BANDWIDTH=12876596,VIDEO-RANGE=PQ,CODECS="dvh1.05.03",RESOLUTION=1920x1080,FRAME-RATE=23.976,CLOSED-CAPTIONS=NONE,HDCP-LEVEL=TYPE-0 +dolby_1080/prog_index.m3u8 +#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=24975091,BANDWIDTH=30041698,VIDEO-RANGE=PQ,CODECS="dvh1.05.06",RESOLUTION=3840x2160,FRAME-RATE=23.976,CLOSED-CAPTIONS=NONE,HDCP-LEVEL=TYPE-1 +dolby_2160/prog_index.m3u8 +#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=3320040,BANDWIDTH=5280654,VIDEO-RANGE=PQ,CODECS="hvc1.2.4.L123.B0",RESOLUTION=1280x720,FRAME-RATE=23.976,CLOSED-CAPTIONS=NONE,HDCP-LEVEL=NONE +hdr10_720/prog_index.m3u8 +#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=7964551,BANDWIDTH=12886714,VIDEO-RANGE=PQ,CODECS="hvc1.2.4.L123.B0",RESOLUTION=1920x1080,FRAME-RATE=23.976,CLOSED-CAPTIONS=NONE,HDCP-LEVEL=TYPE-0 +hdr10_1080/prog_index.m3u8 +#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=24833402,BANDWIDTH=29983769,VIDEO-RANGE=PQ,CODECS="hvc1.2.4.L150.B0",RESOLUTION=3840x2160,FRAME-RATE=23.976,CLOSED-CAPTIONS=NONE,HDCP-LEVEL=TYPE-1 +hdr10_2160/prog_index.m3u8 +#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=248586,BANDWIDTH=593626,VIDEO-RANGE=SDR,CODECS="hvc1.2.4.L123.B0",RESOLUTION=1280x720,HDCP-LEVEL=NONE,URI="sdr_720/iframe_index.m3u8" +#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=399790,BANDWIDTH=956552,VIDEO-RANGE=SDR,CODECS="hvc1.2.4.L123.B0",RESOLUTION=1920x1080,HDCP-LEVEL=TYPE-0,URI="sdr_1080/iframe_index.m3u8" +#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=826971,BANDWIDTH=1941397,VIDEO-RANGE=SDR,CODECS="hvc1.2.4.L150.B0",RESOLUTION=3840x2160,HDCP-LEVEL=TYPE-1,URI="sdr_2160/iframe_index.m3u8" +#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=232253,BANDWIDTH=573073,VIDEO-RANGE=PQ,CODECS="dvh1.05.01",RESOLUTION=1280x720,HDCP-LEVEL=NONE,URI="dolby_720/iframe_index.m3u8" +#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=365337,BANDWIDTH=905037,VIDEO-RANGE=PQ,CODECS="dvh1.05.03",RESOLUTION=1920x1080,HDCP-LEVEL=TYPE-0,URI="dolby_1080/iframe_index.m3u8" +#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=739114,BANDWIDTH=1893236,VIDEO-RANGE=PQ,CODECS="dvh1.05.06",RESOLUTION=3840x2160,HDCP-LEVEL=TYPE-1,URI="dolby_2160/iframe_index.m3u8" +#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=232511,BANDWIDTH=572673,VIDEO-RANGE=PQ,CODECS="hvc1.2.4.L123.B0",RESOLUTION=1280x720,HDCP-LEVEL=NONE,URI="hdr10_720/iframe_index.m3u8" +#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=364552,BANDWIDTH=905053,VIDEO-RANGE=PQ,CODECS="hvc1.2.4.L123.B0",RESOLUTION=1920x1080,HDCP-LEVEL=TYPE-0,URI="hdr10_1080/iframe_index.m3u8" +#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=739757,BANDWIDTH=1895477,VIDEO-RANGE=PQ,CODECS="hvc1.2.4.L150.B0",RESOLUTION=3840x2160,HDCP-LEVEL=TYPE-1,URI="hdr10_2160/iframe_index.m3u8" \ No newline at end of file diff --git a/writer.go b/writer.go index e02cd73a..01e9574d 100644 --- a/writer.go +++ b/writer.go @@ -153,6 +153,10 @@ func (p *MasterPlaylist) Encode() *bytes.Buffer { p.buf.WriteString(strconv.FormatUint(uint64(pl.ProgramId), 10)) p.buf.WriteString(",BANDWIDTH=") p.buf.WriteString(strconv.FormatUint(uint64(pl.Bandwidth), 10)) + if pl.AverageBandwidth != 0 { + p.buf.WriteString(",AVERAGE-BANDWIDTH=") + p.buf.WriteString(strconv.FormatUint(uint64(pl.AverageBandwidth), 10)) + } if pl.Codecs != "" { p.buf.WriteString(",CODECS=\"") p.buf.WriteString(pl.Codecs) @@ -167,6 +171,14 @@ func (p *MasterPlaylist) Encode() *bytes.Buffer { p.buf.WriteString(pl.Video) p.buf.WriteRune('"') } + if pl.VideoRange != "" { + p.buf.WriteString(",VIDEO-RANGE=") + p.buf.WriteString(pl.VideoRange) + } + if pl.HCDPLevel != "" { + p.buf.WriteString(",HDCP-LEVEL=") + p.buf.WriteString(pl.HCDPLevel) + } if pl.URI != "" { p.buf.WriteString(",URI=\"") p.buf.WriteString(pl.URI) @@ -180,7 +192,7 @@ func (p *MasterPlaylist) Encode() *bytes.Buffer { p.buf.WriteString(strconv.FormatUint(uint64(pl.Bandwidth), 10)) if pl.AverageBandwidth != 0 { p.buf.WriteString(",AVERAGE-BANDWIDTH=") - p.buf.WriteString(strconv.FormatUint(uint64(pl.Bandwidth), 10)) + p.buf.WriteString(strconv.FormatUint(uint64(pl.AverageBandwidth), 10)) } if pl.Codecs != "" { p.buf.WriteString(",CODECS=\"") @@ -226,14 +238,12 @@ func (p *MasterPlaylist) Encode() *bytes.Buffer { p.buf.WriteString(strconv.FormatFloat(pl.FrameRate, 'f', 3, 64)) } if pl.VideoRange != "" { - p.buf.WriteString(",VIDEO-RANGE=\"") + p.buf.WriteString(",VIDEO-RANGE=") p.buf.WriteString(pl.VideoRange) - p.buf.WriteRune('"') } if pl.HCDPLevel != "" { - p.buf.WriteString(",HDCP-LEVEL=\"") + p.buf.WriteString(",HDCP-LEVEL=") p.buf.WriteString(pl.HCDPLevel) - p.buf.WriteRune('"') } p.buf.WriteRune('\n') diff --git a/writer_test.go b/writer_test.go index e737000c..350d6b90 100644 --- a/writer_test.go +++ b/writer_test.go @@ -857,42 +857,6 @@ func TestMasterSetVersion(t *testing.T) { } } -// Create new master playlist supporting CLOSED-CAPTIONS=NONE -func TestNewMasterPlaylistWithV7Params(t *testing.T) { - m := NewMasterPlaylist() - - vp := &VariantParams{ - ProgramId: 0, - Bandwidth: 8000, - Codecs: "avc1", - Resolution: "1280x720", - Audio: "audio0", - Captions: "NONE", - VideoRange: "SDR", - FrameRate: 29.97, - HCDPLevel: "NONE", - } - - p, err := NewMediaPlaylist(1, 1) - if err != nil { - t.Fatalf("Create media playlist failed: %s", err) - } - m.Append(fmt.Sprintf("eng_rendition_rendition.m3u8"), p, *vp) - - expected := "CLOSED-CAPTIONS=NONE" - if !strings.Contains(m.String(), expected) { - t.Fatalf("Master playlist did not contain: %s\nMaster Playlist:\n%v", expected, m.String()) - } - // quotes need to be include if not eq NONE - vp.Captions = "CC1" - m2 := NewMasterPlaylist() - m2.Append(fmt.Sprintf("eng_rendition_rendition.m3u8"), p, *vp) - expected = `CLOSED-CAPTIONS="CC1"` - if !strings.Contains(m2.String(), expected) { - t.Fatalf("Master playlist did not contain: %s\nMaster Playlist:\n%v", expected, m2.String()) - } -} - /****************************** * Code generation examples * ******************************/ @@ -975,6 +939,23 @@ func ExampleMasterPlaylist_String() { // chunklist2.m3u8 } +func ExampleMasterPlaylist_String_with_hlsv7() { + m := NewMasterPlaylist() + m.SetVersion(7) + m.SetIndependentSegments(true) + p, _ := NewMediaPlaylist(3, 5) + m.Append("hdr10_1080/prog_index.m3u8", p, VariantParams{AverageBandwidth: 7964551, Bandwidth: 12886714, VideoRange: "PQ", Codecs: "hvc1.2.4.L123.B0", Resolution: "1920x1080", FrameRate: 23.976, Captions: "NONE", HCDPLevel: "TYPE-0"}) + m.Append("hdr10_1080/iframe_index.m3u8", p, VariantParams{Iframe: true, AverageBandwidth: 364552, Bandwidth: 905053, VideoRange: "PQ", Codecs: "hvc1.2.4.L123.B0", Resolution: "1920x1080", HCDPLevel: "TYPE-0"}) + fmt.Printf("%s", m) + // Output: + // #EXTM3U + // #EXT-X-VERSION:7 + // #EXT-X-INDEPENDENT-SEGMENTS + // #EXT-X-STREAM-INF:PROGRAM-ID=0,BANDWIDTH=12886714,AVERAGE-BANDWIDTH=7964551,CODECS="hvc1.2.4.L123.B0",RESOLUTION=1920x1080,CLOSED-CAPTIONS=NONE,FRAME-RATE=23.976,VIDEO-RANGE=PQ,HDCP-LEVEL=TYPE-0 + // hdr10_1080/prog_index.m3u8 + // #EXT-X-I-FRAME-STREAM-INF:PROGRAM-ID=0,BANDWIDTH=905053,AVERAGE-BANDWIDTH=364552,CODECS="hvc1.2.4.L123.B0",RESOLUTION=1920x1080,VIDEO-RANGE=PQ,HDCP-LEVEL=TYPE-0,URI="hdr10_1080/iframe_index.m3u8" +} + func ExampleMediaPlaylist_Segments_scte35_oatcls() { f, _ := os.Open("sample-playlists/media-playlist-with-oatcls-scte35.m3u8") p, _, _ := DecodeFrom(bufio.NewReader(f), true) From ef75d6ee07dfd35e135c72e64891a25273623e23 Mon Sep 17 00:00:00 2001 From: Bobby Peck Date: Fri, 19 Apr 2019 10:09:14 -0700 Subject: [PATCH 6/7] Undo accidental deletion --- reader_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/reader_test.go b/reader_test.go index 8b32a88b..ab09c13d 100644 --- a/reader_test.go +++ b/reader_test.go @@ -489,7 +489,7 @@ func TestDecodeMediaPlaylistAutoDetectExtend(t *testing.T) { // Test for FullTimeParse of EXT-X-PROGRAM-DATE-TIME // We testing ISO/IEC 8601:2004 where we can get time in UTC, UTC with Nanoseconds // timeZone in formats '±00:00', '±0000', '±00' -// FullTimeParse() +// m3u8.FullTimeParse() func TestFullTimeParse(t *testing.T) { var timestamps = []struct { name string @@ -517,7 +517,7 @@ func TestFullTimeParse(t *testing.T) { // Test for StrictTimeParse of EXT-X-PROGRAM-DATE-TIME // We testing Strict format of RFC3339 where we can get time in UTC, UTC with Nanoseconds // timeZone in formats '±00:00', '±0000', '±00' -// StrictTimeParse() +// m3u8.StrictTimeParse() func TestStrictTimeParse(t *testing.T) { var timestamps = []struct { name string From 6ae1e0fe86284ffcec9b387071685ce821d131bd Mon Sep 17 00:00:00 2001 From: Bobby Peck Date: Fri, 19 Apr 2019 10:26:57 -0700 Subject: [PATCH 7/7] Fix HDCPLevel typo --- reader.go | 4 ++-- reader_test.go | 36 ++++++++++++++++++------------------ structure.go | 2 +- writer.go | 8 ++++---- writer_test.go | 4 ++-- 5 files changed, 27 insertions(+), 27 deletions(-) diff --git a/reader.go b/reader.go index 9deee4f0..84b6827c 100644 --- a/reader.go +++ b/reader.go @@ -315,7 +315,7 @@ func decodeLineOfMasterPlaylist(p *MasterPlaylist, state *decodingState, line st case "VIDEO-RANGE": state.variant.VideoRange = v case "HDCP-LEVEL": - state.variant.HCDPLevel = v + state.variant.HDCPLevel = v } } case state.tagStreamInf && !strings.HasPrefix(line, "#"): @@ -366,7 +366,7 @@ func decodeLineOfMasterPlaylist(p *MasterPlaylist, state *decodingState, line st case "VIDEO-RANGE": state.variant.VideoRange = v case "HDCP-LEVEL": - state.variant.HCDPLevel = v + state.variant.HDCPLevel = v } } case strings.HasPrefix(line, "#"): // unknown tags treated as comments diff --git a/reader_test.go b/reader_test.go index ab09c13d..885e7d28 100644 --- a/reader_test.go +++ b/reader_test.go @@ -245,24 +245,24 @@ func TestDecodeMasterWithHLSV7(t *testing.T) { } var unexpected []*Variant expected := map[string]VariantParams{ - "sdr_720/prog_index.m3u8": {Bandwidth: 3971374, AverageBandwidth: 2778321, Codecs: "hvc1.2.4.L123.B0", Resolution: "1280x720", Captions: "NONE", VideoRange: "SDR", HCDPLevel: "NONE", FrameRate: 23.976}, - "sdr_1080/prog_index.m3u8": {Bandwidth: 10022043, AverageBandwidth: 6759875, Codecs: "hvc1.2.4.L123.B0", Resolution: "1920x1080", Captions: "NONE", VideoRange: "SDR", HCDPLevel: "TYPE-0", FrameRate: 23.976}, - "sdr_2160/prog_index.m3u8": {Bandwidth: 28058971, AverageBandwidth: 20985770, Codecs: "hvc1.2.4.L150.B0", Resolution: "3840x2160", Captions: "NONE", VideoRange: "SDR", HCDPLevel: "TYPE-1", FrameRate: 23.976}, - "dolby_720/prog_index.m3u8": {Bandwidth: 5327059, AverageBandwidth: 3385450, Codecs: "dvh1.05.01", Resolution: "1280x720", Captions: "NONE", VideoRange: "PQ", HCDPLevel: "NONE", FrameRate: 23.976}, - "dolby_1080/prog_index.m3u8": {Bandwidth: 12876596, AverageBandwidth: 7999361, Codecs: "dvh1.05.03", Resolution: "1920x1080", Captions: "NONE", VideoRange: "PQ", HCDPLevel: "TYPE-0", FrameRate: 23.976}, - "dolby_2160/prog_index.m3u8": {Bandwidth: 30041698, AverageBandwidth: 24975091, Codecs: "dvh1.05.06", Resolution: "3840x2160", Captions: "NONE", VideoRange: "PQ", HCDPLevel: "TYPE-1", FrameRate: 23.976}, - "hdr10_720/prog_index.m3u8": {Bandwidth: 5280654, AverageBandwidth: 3320040, Codecs: "hvc1.2.4.L123.B0", Resolution: "1280x720", Captions: "NONE", VideoRange: "PQ", HCDPLevel: "NONE", FrameRate: 23.976}, - "hdr10_1080/prog_index.m3u8": {Bandwidth: 12886714, AverageBandwidth: 7964551, Codecs: "hvc1.2.4.L123.B0", Resolution: "1920x1080", Captions: "NONE", VideoRange: "PQ", HCDPLevel: "TYPE-0", FrameRate: 23.976}, - "hdr10_2160/prog_index.m3u8": {Bandwidth: 29983769, AverageBandwidth: 24833402, Codecs: "hvc1.2.4.L150.B0", Resolution: "3840x2160", Captions: "NONE", VideoRange: "PQ", HCDPLevel: "TYPE-1", FrameRate: 23.976}, - "sdr_720/iframe_index.m3u8": {Bandwidth: 593626, AverageBandwidth: 248586, Codecs: "hvc1.2.4.L123.B0", Resolution: "1280x720", Iframe: true, VideoRange: "SDR", HCDPLevel: "NONE"}, - "sdr_1080/iframe_index.m3u8": {Bandwidth: 956552, AverageBandwidth: 399790, Codecs: "hvc1.2.4.L123.B0", Resolution: "1920x1080", Iframe: true, VideoRange: "SDR", HCDPLevel: "TYPE-0"}, - "sdr_2160/iframe_index.m3u8": {Bandwidth: 1941397, AverageBandwidth: 826971, Codecs: "hvc1.2.4.L150.B0", Resolution: "3840x2160", Iframe: true, VideoRange: "SDR", HCDPLevel: "TYPE-1"}, - "dolby_720/iframe_index.m3u8": {Bandwidth: 573073, AverageBandwidth: 232253, Codecs: "dvh1.05.01", Resolution: "1280x720", Iframe: true, VideoRange: "PQ", HCDPLevel: "NONE"}, - "dolby_1080/iframe_index.m3u8": {Bandwidth: 905037, AverageBandwidth: 365337, Codecs: "dvh1.05.03", Resolution: "1920x1080", Iframe: true, VideoRange: "PQ", HCDPLevel: "TYPE-0"}, - "dolby_2160/iframe_index.m3u8": {Bandwidth: 1893236, AverageBandwidth: 739114, Codecs: "dvh1.05.06", Resolution: "3840x2160", Iframe: true, VideoRange: "PQ", HCDPLevel: "TYPE-1"}, - "hdr10_720/iframe_index.m3u8": {Bandwidth: 572673, AverageBandwidth: 232511, Codecs: "hvc1.2.4.L123.B0", Resolution: "1280x720", Iframe: true, VideoRange: "PQ", HCDPLevel: "NONE"}, - "hdr10_1080/iframe_index.m3u8": {Bandwidth: 905053, AverageBandwidth: 364552, Codecs: "hvc1.2.4.L123.B0", Resolution: "1920x1080", Iframe: true, VideoRange: "PQ", HCDPLevel: "TYPE-0"}, - "hdr10_2160/iframe_index.m3u8": {Bandwidth: 1895477, AverageBandwidth: 739757, Codecs: "hvc1.2.4.L150.B0", Resolution: "3840x2160", Iframe: true, VideoRange: "PQ", HCDPLevel: "TYPE-1"}, + "sdr_720/prog_index.m3u8": {Bandwidth: 3971374, AverageBandwidth: 2778321, Codecs: "hvc1.2.4.L123.B0", Resolution: "1280x720", Captions: "NONE", VideoRange: "SDR", HDCPLevel: "NONE", FrameRate: 23.976}, + "sdr_1080/prog_index.m3u8": {Bandwidth: 10022043, AverageBandwidth: 6759875, Codecs: "hvc1.2.4.L123.B0", Resolution: "1920x1080", Captions: "NONE", VideoRange: "SDR", HDCPLevel: "TYPE-0", FrameRate: 23.976}, + "sdr_2160/prog_index.m3u8": {Bandwidth: 28058971, AverageBandwidth: 20985770, Codecs: "hvc1.2.4.L150.B0", Resolution: "3840x2160", Captions: "NONE", VideoRange: "SDR", HDCPLevel: "TYPE-1", FrameRate: 23.976}, + "dolby_720/prog_index.m3u8": {Bandwidth: 5327059, AverageBandwidth: 3385450, Codecs: "dvh1.05.01", Resolution: "1280x720", Captions: "NONE", VideoRange: "PQ", HDCPLevel: "NONE", FrameRate: 23.976}, + "dolby_1080/prog_index.m3u8": {Bandwidth: 12876596, AverageBandwidth: 7999361, Codecs: "dvh1.05.03", Resolution: "1920x1080", Captions: "NONE", VideoRange: "PQ", HDCPLevel: "TYPE-0", FrameRate: 23.976}, + "dolby_2160/prog_index.m3u8": {Bandwidth: 30041698, AverageBandwidth: 24975091, Codecs: "dvh1.05.06", Resolution: "3840x2160", Captions: "NONE", VideoRange: "PQ", HDCPLevel: "TYPE-1", FrameRate: 23.976}, + "hdr10_720/prog_index.m3u8": {Bandwidth: 5280654, AverageBandwidth: 3320040, Codecs: "hvc1.2.4.L123.B0", Resolution: "1280x720", Captions: "NONE", VideoRange: "PQ", HDCPLevel: "NONE", FrameRate: 23.976}, + "hdr10_1080/prog_index.m3u8": {Bandwidth: 12886714, AverageBandwidth: 7964551, Codecs: "hvc1.2.4.L123.B0", Resolution: "1920x1080", Captions: "NONE", VideoRange: "PQ", HDCPLevel: "TYPE-0", FrameRate: 23.976}, + "hdr10_2160/prog_index.m3u8": {Bandwidth: 29983769, AverageBandwidth: 24833402, Codecs: "hvc1.2.4.L150.B0", Resolution: "3840x2160", Captions: "NONE", VideoRange: "PQ", HDCPLevel: "TYPE-1", FrameRate: 23.976}, + "sdr_720/iframe_index.m3u8": {Bandwidth: 593626, AverageBandwidth: 248586, Codecs: "hvc1.2.4.L123.B0", Resolution: "1280x720", Iframe: true, VideoRange: "SDR", HDCPLevel: "NONE"}, + "sdr_1080/iframe_index.m3u8": {Bandwidth: 956552, AverageBandwidth: 399790, Codecs: "hvc1.2.4.L123.B0", Resolution: "1920x1080", Iframe: true, VideoRange: "SDR", HDCPLevel: "TYPE-0"}, + "sdr_2160/iframe_index.m3u8": {Bandwidth: 1941397, AverageBandwidth: 826971, Codecs: "hvc1.2.4.L150.B0", Resolution: "3840x2160", Iframe: true, VideoRange: "SDR", HDCPLevel: "TYPE-1"}, + "dolby_720/iframe_index.m3u8": {Bandwidth: 573073, AverageBandwidth: 232253, Codecs: "dvh1.05.01", Resolution: "1280x720", Iframe: true, VideoRange: "PQ", HDCPLevel: "NONE"}, + "dolby_1080/iframe_index.m3u8": {Bandwidth: 905037, AverageBandwidth: 365337, Codecs: "dvh1.05.03", Resolution: "1920x1080", Iframe: true, VideoRange: "PQ", HDCPLevel: "TYPE-0"}, + "dolby_2160/iframe_index.m3u8": {Bandwidth: 1893236, AverageBandwidth: 739114, Codecs: "dvh1.05.06", Resolution: "3840x2160", Iframe: true, VideoRange: "PQ", HDCPLevel: "TYPE-1"}, + "hdr10_720/iframe_index.m3u8": {Bandwidth: 572673, AverageBandwidth: 232511, Codecs: "hvc1.2.4.L123.B0", Resolution: "1280x720", Iframe: true, VideoRange: "PQ", HDCPLevel: "NONE"}, + "hdr10_1080/iframe_index.m3u8": {Bandwidth: 905053, AverageBandwidth: 364552, Codecs: "hvc1.2.4.L123.B0", Resolution: "1920x1080", Iframe: true, VideoRange: "PQ", HDCPLevel: "TYPE-0"}, + "hdr10_2160/iframe_index.m3u8": {Bandwidth: 1895477, AverageBandwidth: 739757, Codecs: "hvc1.2.4.L150.B0", Resolution: "3840x2160", Iframe: true, VideoRange: "PQ", HDCPLevel: "TYPE-1"}, } for _, variant := range p.Variants { var found bool diff --git a/structure.go b/structure.go index 2a40ffb7..cedbbd0d 100644 --- a/structure.go +++ b/structure.go @@ -171,7 +171,7 @@ type VariantParams struct { Name string // EXT-X-STREAM-INF only (non standard Wowza/JWPlayer extension to name the variant/quality in UA) Iframe bool // EXT-X-I-FRAME-STREAM-INF VideoRange string - HCDPLevel string + HDCPLevel string FrameRate float64 // EXT-X-STREAM-INF Alternatives []*Alternative // EXT-X-MEDIA } diff --git a/writer.go b/writer.go index 01e9574d..dd796df0 100644 --- a/writer.go +++ b/writer.go @@ -175,9 +175,9 @@ func (p *MasterPlaylist) Encode() *bytes.Buffer { p.buf.WriteString(",VIDEO-RANGE=") p.buf.WriteString(pl.VideoRange) } - if pl.HCDPLevel != "" { + if pl.HDCPLevel != "" { p.buf.WriteString(",HDCP-LEVEL=") - p.buf.WriteString(pl.HCDPLevel) + p.buf.WriteString(pl.HDCPLevel) } if pl.URI != "" { p.buf.WriteString(",URI=\"") @@ -241,9 +241,9 @@ func (p *MasterPlaylist) Encode() *bytes.Buffer { p.buf.WriteString(",VIDEO-RANGE=") p.buf.WriteString(pl.VideoRange) } - if pl.HCDPLevel != "" { + if pl.HDCPLevel != "" { p.buf.WriteString(",HDCP-LEVEL=") - p.buf.WriteString(pl.HCDPLevel) + p.buf.WriteString(pl.HDCPLevel) } p.buf.WriteRune('\n') diff --git a/writer_test.go b/writer_test.go index 350d6b90..8a9410bc 100644 --- a/writer_test.go +++ b/writer_test.go @@ -944,8 +944,8 @@ func ExampleMasterPlaylist_String_with_hlsv7() { m.SetVersion(7) m.SetIndependentSegments(true) p, _ := NewMediaPlaylist(3, 5) - m.Append("hdr10_1080/prog_index.m3u8", p, VariantParams{AverageBandwidth: 7964551, Bandwidth: 12886714, VideoRange: "PQ", Codecs: "hvc1.2.4.L123.B0", Resolution: "1920x1080", FrameRate: 23.976, Captions: "NONE", HCDPLevel: "TYPE-0"}) - m.Append("hdr10_1080/iframe_index.m3u8", p, VariantParams{Iframe: true, AverageBandwidth: 364552, Bandwidth: 905053, VideoRange: "PQ", Codecs: "hvc1.2.4.L123.B0", Resolution: "1920x1080", HCDPLevel: "TYPE-0"}) + m.Append("hdr10_1080/prog_index.m3u8", p, VariantParams{AverageBandwidth: 7964551, Bandwidth: 12886714, VideoRange: "PQ", Codecs: "hvc1.2.4.L123.B0", Resolution: "1920x1080", FrameRate: 23.976, Captions: "NONE", HDCPLevel: "TYPE-0"}) + m.Append("hdr10_1080/iframe_index.m3u8", p, VariantParams{Iframe: true, AverageBandwidth: 364552, Bandwidth: 905053, VideoRange: "PQ", Codecs: "hvc1.2.4.L123.B0", Resolution: "1920x1080", HDCPLevel: "TYPE-0"}) fmt.Printf("%s", m) // Output: // #EXTM3U