Skip to content

Commit

Permalink
Merge branch 'master' into draft
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexander I.Grafov committed May 2, 2016
2 parents ac1fd13 + c98e584 commit cce0a8e
Show file tree
Hide file tree
Showing 11 changed files with 357 additions and 131 deletions.
8 changes: 6 additions & 2 deletions AUTHORS
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
There are several persons contribute their code include small patches
There are many persons contribute their code (include small patches)
to the project. They listed below in an alphabetical order:

- Alexander (Axel) I.Grafov <grafov@gmail.com>
- Andrey Chernov <chernov@bradburylab.com>
- Andrew Sinclair <ajsinclair@gmail.com>
- Bradley Falzon <brad@teambrad.net>
- Denys Smirnov <denis.smirnov.91@gmail.com>
- Fabrizio (Misto) Milo <mistobaan@gmail.com>
- Hori Ryota <hori.ryota@gmail.com>
- Jamie Stackhouse <jamie.stackhouse@redspace.com>
- Julian Cooper <jcooper@brightcove.com>
- Kz26
- Makombo
- Scott Kidder <skidder@brightcove.com>
- Vishal Kumar Tuniki <vishal24.tuniki@gmail.com>
- Zac Shenker <zshenker@brightcove.com>

If you want to be added to this list (or removed for any reason)
just open issue about it.
just open an issue about it.
2 changes: 1 addition & 1 deletion M3U8.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,4 @@ http://tools.ietf.org/html/draft-pantos-http-live-streaming
* Version 4 of the HLS protocol described in draft07-draft08.
* Version 5 of the HLS protocol described in draft09-draft11.
* Version 6 of the HLS protocol described in draft12-draft13.
* Version 7 of the HLS protocol described in draft14-draft17.
* Version 7 of the HLS protocol described in draft14-draft19.
25 changes: 12 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,13 @@ Related links
Library usage
-------------

This library successfully used in streaming software developed for my employer and tested with
generating of VOD and Live streams and parsing of Widevine Live streams. Also library usage noted
in opensource software:
This library was successfully used in streaming software developed for company where I worked several
years ago. It was tested then in generating of VOD and Live streams and parsing of Widevine Live streams.
Also the library used in opensource software so you may look at these apps for usage examples:

* [Stream Surfer](http://streamsurfer.org) monitoring software.
* [gohls](https://github.com/kz26/gohls) — HLS downloader.
* [HLS downloader](https://github.com/kz26/gohls)
* [Another HLS downloader](https://github.com/Makombo/hlsdownloader)
* [HLS utils](https://github.com/archsh/hls-utils)

M3U8 parsing/generation in other languages
------------------------------------------
Expand All @@ -116,20 +117,18 @@ M3U8 parsing/generation in other languages
* http://sourceforge.net/projects/m3u8parser/ in Java
* https://github.com/karlll/erlm3u8 in Erlang

Project status [![Is maintained?](http://stillmaintained.com/grafov/m3u8.png)](http://stillmaintained.com/grafov/m3u8)
---------------
Project status [![Go Report Card](https://goreportcard.com/badge/grafov/m3u8)](https://goreportcard.com/report/grafov/m3u8)
--------------

In development.
[![Build Status](https://travis-ci.org/grafov/m3u8.png?branch=master)](https://travis-ci.org/grafov/m3u8) [![Build Status](https://drone.io/github.com/grafov/m3u8/status.png)](https://drone.io/github.com/grafov/m3u8/latest)

[![Build Status](https://travis-ci.org/grafov/m3u8.png?branch=master)](https://travis-ci.org/grafov/m3u8) for last commit from `master` or `draft` branches.
Project maintainers:

[![Build Status](https://drone.io/github.com/grafov/m3u8/status.png)](https://drone.io/github.com/grafov/m3u8/latest) for `master` branch.
* Bradley Falzon @bradleyfalzon
* Alexander Grafov @grafov

Development rules:

* Feature changes first applied to `draft` branch then after minimal testing it will merge with `master` branch.
* Bugfixes or minor doc changes applied to `master` branch and then merged to `draft`.
* Code in `draft` branch may be in an inconsistent state.
* After complete testing and one week usage with my prober for HLS [Stream Surfer](http://streamsurfer.org) it may be released as new library version.
* Each new API call or M3U8 tag accompanied by at least with one unit test till new release (this rule will be apply after v1.0).
* Versioning scheme follows http://semver.org rules (but versions till v1.0 not support bacward compatibility, see release notes carefully).
Expand Down
2 changes: 1 addition & 1 deletion doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ M3U8 is simple text format and parsing library for it must be simple too. It did
* Encryption keys support for usage with DRM systems like Verimatrix (http://verimatrix.com) etc.
* Support for non standard Google Widevine (http://www.widevine.com) tags.
Library coded acordingly with IETF draft http://tools.ietf.org/html/draft-pantos-http-live-streaming
Library coded accordingly with IETF draft http://tools.ietf.org/html/draft-pantos-http-live-streaming
Examples of usage may be found in *_test.go files of a package. Also see below some simple examples.
Expand Down
43 changes: 31 additions & 12 deletions reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,8 +212,6 @@ func decodeParamsLine(line string) map[string]string {

// Parse one line of master playlist.
func decodeLineOfMasterPlaylist(p *MasterPlaylist, state *decodingState, line string, strict bool) error {
var alt *Alternative
var alternatives []*Alternative
var err error

line = strings.TrimSpace(line)
Expand All @@ -228,9 +226,8 @@ func decodeLineOfMasterPlaylist(p *MasterPlaylist, state *decodingState, line st
return err
}
case strings.HasPrefix(line, "#EXT-X-MEDIA:"):
var alt Alternative
state.listType = MASTER
alt = new(Alternative)
alternatives = append(alternatives, alt)
for k, v := range decodeParamsLine(line[13:]) {
switch k {
case "TYPE":
Expand Down Expand Up @@ -261,13 +258,14 @@ func decodeLineOfMasterPlaylist(p *MasterPlaylist, state *decodingState, line st
alt.URI = v
}
}
state.alternatives = append(state.alternatives, &alt)
case !state.tagStreamInf && strings.HasPrefix(line, "#EXT-X-STREAM-INF:"):
state.tagStreamInf = true
state.listType = MASTER
state.variant = new(Variant)
if len(alternatives) > 0 {
state.variant.Alternatives = alternatives
alternatives = nil
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:]) {
Expand Down Expand Up @@ -310,9 +308,9 @@ func decodeLineOfMasterPlaylist(p *MasterPlaylist, state *decodingState, line st
state.listType = MASTER
state.variant = new(Variant)
state.variant.Iframe = true
if len(alternatives) > 0 {
state.variant.Alternatives = alternatives
alternatives = nil
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:]) {
Expand Down Expand Up @@ -443,6 +441,20 @@ func decodeLineOfMediaPlaylist(p *MediaPlaylist, wv *WV, state *decodingState, l
return fmt.Errorf("Byterange sub-range offset value parsing error: %s", err)
}
}
case !state.tagSCTE35 && strings.HasPrefix(line, "#EXT-SCTE35:"):
state.tagSCTE35 = true
state.listType = MEDIA
state.scte = new(SCTE)
for attribute, value := range decodeParamsLine(line[12:]) {
switch attribute {
case "CUE":
state.scte.Cue = value
case "ID":
state.scte.ID = value
case "TIME":
state.scte.Time, _ = strconv.ParseFloat(value, 64)
}
}
case !state.tagInf && strings.HasPrefix(line, "#EXTINF:"):
state.tagInf = true
state.listType = MEDIA
Expand Down Expand Up @@ -471,6 +483,13 @@ func decodeLineOfMediaPlaylist(p *MediaPlaylist, wv *WV, state *decodingState, l
}
state.tagRange = false
}
if state.tagSCTE35 {
state.tagSCTE35 = false
scte := *state.scte
if err = p.SetSCTE(scte.Cue, scte.ID, scte.Time); strict && err != nil {
return err
}
}
if state.tagDiscontinuity {
state.tagDiscontinuity = false
if err = p.SetDiscontinuity(); strict && err != nil {
Expand All @@ -485,7 +504,7 @@ func decodeLineOfMediaPlaylist(p *MediaPlaylist, wv *WV, state *decodingState, l
}
// If EXT-X-KEY appeared before reference to segment (EXTINF) then it linked to this segment
if state.tagKey {
p.Segments[(p.tail-1)%p.capacity].Key = &Key{state.xkey.Method, state.xkey.URI, state.xkey.IV, state.xkey.Keyformat, state.xkey.Keyformatversions}
p.Segments[p.last()].Key = &Key{state.xkey.Method, state.xkey.URI, state.xkey.IV, state.xkey.Keyformat, state.xkey.Keyformatversions}
// First EXT-X-KEY may appeared in the header of the playlist and linked to first segment
// but for convenient playlist generation it also linked as default playlist key
if p.Key == nil {
Expand All @@ -495,7 +514,7 @@ func decodeLineOfMediaPlaylist(p *MediaPlaylist, wv *WV, state *decodingState, l
}
// If EXT-X-MAP appeared before reference to segment (EXTINF) then it linked to this segment
if state.tagMap {
p.Segments[(p.tail-1)%p.capacity].Map = &Map{state.xmap.URI, state.xmap.Limit, state.xmap.Offset}
p.Segments[p.last()].Map = &Map{state.xmap.URI, state.xmap.Limit, state.xmap.Offset}
// First EXT-X-MAP may appeared in the header of the playlist and linked to first segment
// but for convenient playlist generation it also linked as default playlist map
if p.Map == nil {
Expand Down
72 changes: 67 additions & 5 deletions reader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,25 @@ func TestDecodeMasterPlaylistWithAlternatives(t *testing.T) {
if p.ver != 3 {
t.Errorf("Version of parsed playlist = %d (must = 3)", p.ver)
}
// if len(p.Variants) != 5 {
// t.Fatal("Not all variants in master playlist parsed.")
// }
if len(p.Variants) != 4 {
t.Fatal("not all variants in master playlist parsed")
}
// TODO check other values
//fmt.Println(p.Encode().String())
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 == 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 == 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 == 3 && len(v.Alternatives) > 0 {
t.Fatal("should not be alternatives for this variant")
}
}
// fmt.Println(p.Encode().String())
}

// Decode a master playlist with Name tag in EXT-X-STREAM-INF
Expand Down Expand Up @@ -231,7 +245,7 @@ func ExampleMediaPlaylist_DurationAsInt() {
// Output:
// #EXTM3U
// #EXT-X-VERSION:3
// #EXT-X-MEDIA-SEQUENCE:1
// #EXT-X-MEDIA-SEQUENCE:0
// #EXT-X-TARGETDURATION:10
// #EXTINF:10,
// ad0.ts
Expand All @@ -243,3 +257,51 @@ func ExampleMediaPlaylist_DurationAsInt() {
// #EXTINF:10,
// movieB.ts
}

func TestMediaPlaylistWithSCTE35Tag(t *testing.T) {
test_cases := []struct {
playlistLocation string
expectedSCTEIndex int
expectedSCTECue string
expectedSCTEID string
expectedSCTETime float64
}{
{
"sample-playlists/media-playlist-with-scte35.m3u8",
2,
"/DAIAAAAAAAAAAAQAAZ/I0VniQAQAgBDVUVJQAAAAH+cAAAAAA==",
"123",
123.12,
},
{
"sample-playlists/media-playlist-with-scte35-1.m3u8",
1,
"/DAIAAAAAAAAAAAQAAZ/I0VniQAQAgBDVUVJQAA",
"",
0,
},
}
for _, c := range test_cases {
f, _ := os.Open(c.playlistLocation)
playlist, _, _ := DecodeFrom(bufio.NewReader(f), true)
mediaPlaylist := playlist.(*MediaPlaylist)
for index, item := range mediaPlaylist.Segments {
if item == nil {
break
}
if index != c.expectedSCTEIndex && item.SCTE != nil {
t.Error("Not expecting SCTE information on this segment")
} else if index == c.expectedSCTEIndex && item.SCTE == nil {
t.Error("Expecting SCTE information on this segment")
} else if index == c.expectedSCTEIndex && item.SCTE != nil {
if (*item.SCTE).Cue != c.expectedSCTECue {
t.Error("Expected ", c.expectedSCTECue, " got ", (*item.SCTE).Cue)
} else if (*item.SCTE).ID != c.expectedSCTEID {
t.Error("Expected ", c.expectedSCTEID, " got ", (*item.SCTE).ID)
} else if (*item.SCTE).Time != c.expectedSCTETime {
t.Error("Expected ", c.expectedSCTETime, " got ", (*item.SCTE).Time)
}
}
}
}
}
11 changes: 11 additions & 0 deletions sample-playlists/media-playlist-with-scte35-1.m3u8
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#EXTM3U
#EXT-X-TARGETDURATION:10
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:10.000,
media0.ts
#EXT-SCTE35: CUE="/DAIAAAAAAAAAAAQAAZ/I0VniQAQAgBDVUVJQAA"
#EXTINF:10.000,
media1.ts
#EXTINF:10.000,
media2.ts
11 changes: 11 additions & 0 deletions sample-playlists/media-playlist-with-scte35.m3u8
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#EXTM3U
#EXT-X-TARGETDURATION:10
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:10.000,
media0.ts
#EXTINF:10.000,
media1.ts
#EXT-SCTE35: CUE="/DAIAAAAAAAAAAAQAAZ/I0VniQAQAgBDVUVJQAAAAH+cAAAAAA==", ID="123", TIME=123.12
#EXTINF:10.000,
media2.ts
22 changes: 16 additions & 6 deletions structure.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ type Variant struct {
VariantParams
}

// This stucture represents additional parameters for a variant
// 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
Expand All @@ -154,11 +154,11 @@ 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)
Iframe bool // EXT-X-I-FRAME-STREAM-INF
Alternatives []*Alternative
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
}

// This structure represents EXT-X-MEDIA tag in variants.
Expand Down Expand Up @@ -188,9 +188,16 @@ type MediaSegment struct {
Key *Key // EXT-X-KEY displayed before the segment and means changing of encryption key (in theory each segment may have own key)
Map *Map // EXT-X-MAP displayed before the segment
Discontinuity bool // EXT-X-DISCONTINUITY indicates an encoding discontinuity between the media segment that follows it and the one that preceded it (i.e. file format, number and type of tracks, encoding parameters, encoding sequence, timestamp sequence)
SCTE *SCTE // EXT-SCTE35 used for Ad signaling in HLS
ProgramDateTime time.Time // EXT-X-PROGRAM-DATE-TIME tag associates the first sample of a media segment with an absolute date and/or time
}

type SCTE struct {
Cue string
ID string
Time float64
}

// This structure represents information about stream encryption.
//
// Realizes EXT-X-KEY tag.
Expand Down Expand Up @@ -252,6 +259,7 @@ type decodingState struct {
tagStreamInf bool
tagIframeStreamInf bool
tagInf bool
tagSCTE35 bool
tagRange bool
tagDiscontinuity bool
tagProgramDateTime bool
Expand All @@ -262,6 +270,8 @@ type decodingState struct {
offset int64
duration float64
variant *Variant
alternatives []*Alternative
xkey *Key
xmap *Map
scte *SCTE
}

0 comments on commit cce0a8e

Please sign in to comment.