Skip to content

Commit

Permalink
Merge pull request #132 from soldiermoth/hlsv7-tags
Browse files Browse the repository at this point in the history
Support HLSv7 tags: VIDEO-RANGE & HDCP-LEVEL
  • Loading branch information
leikao committed May 13, 2019
2 parents 1d88be5 + 6ae1e0f commit 920643e
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 5 deletions.
15 changes: 15 additions & 0 deletions reader.go
Expand Up @@ -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.HDCPLevel = v
}
}
case state.tagStreamInf && !strings.HasPrefix(line, "#"):
Expand Down Expand Up @@ -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.HDCPLevel = v
}
}
case strings.HasPrefix(line, "#"): // unknown tags treated as comments
Expand Down
54 changes: 54 additions & 0 deletions reader_test.go
Expand Up @@ -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", 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
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 *
****************************/
Expand Down
32 changes: 32 additions & 0 deletions 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"
10 changes: 6 additions & 4 deletions structure.go
Expand Up @@ -168,11 +168,13 @@ type VariantParams struct {
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)
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
VideoRange string
HDCPLevel string
FrameRate float64 // EXT-X-STREAM-INF
Iframe bool // EXT-X-I-FRAME-STREAM-INF
Alternatives []*Alternative // EXT-X-MEDIA
}

Expand Down
23 changes: 22 additions & 1 deletion writer.go
Expand Up @@ -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)
Expand All @@ -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.HDCPLevel != "" {
p.buf.WriteString(",HDCP-LEVEL=")
p.buf.WriteString(pl.HDCPLevel)
}
if pl.URI != "" {
p.buf.WriteString(",URI=\"")
p.buf.WriteString(pl.URI)
Expand All @@ -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=\"")
Expand Down Expand Up @@ -225,6 +237,15 @@ func (p *MasterPlaylist) Encode() *bytes.Buffer {
p.buf.WriteString(",FRAME-RATE=")
p.buf.WriteString(strconv.FormatFloat(pl.FrameRate, 'f', 3, 64))
}
if pl.VideoRange != "" {
p.buf.WriteString(",VIDEO-RANGE=")
p.buf.WriteString(pl.VideoRange)
}
if pl.HDCPLevel != "" {
p.buf.WriteString(",HDCP-LEVEL=")
p.buf.WriteString(pl.HDCPLevel)
}

p.buf.WriteRune('\n')
p.buf.WriteString(pl.URI)
if p.Args != "" {
Expand Down
17 changes: 17 additions & 0 deletions writer_test.go
Expand Up @@ -954,6 +954,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", 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
// #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)
Expand Down

0 comments on commit 920643e

Please sign in to comment.