Skip to content

Commit

Permalink
Merge pull request #8 from jwplayer/feature/SSAI-217-EXT-X-INDEPENDEN…
Browse files Browse the repository at this point in the history
…T-SEGMENTS

Add independent segments for MediaPlaylist
  • Loading branch information
azombor authored Aug 14, 2023
2 parents 10d72f3 + 067bfef commit c08fb32
Show file tree
Hide file tree
Showing 6 changed files with 200 additions and 24 deletions.
2 changes: 2 additions & 0 deletions reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,8 @@ func decodeLineOfMediaPlaylist(p *MediaPlaylist, wv *WV, state *decodingState, l
case line == "#EXT-X-ENDLIST":
state.listType = MEDIA
p.Closed = true
case line == "#EXT-X-INDEPENDENT-SEGMENTS":
p.SetIndependentSegments(true)
case strings.HasPrefix(line, "#EXT-X-VERSION:"):
state.listType = MEDIA
if _, err = fmt.Sscanf(line, "#EXT-X-VERSION:%d", &p.ver); strict && err != nil {
Expand Down
72 changes: 72 additions & 0 deletions reader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1132,3 +1132,75 @@ func BenchmarkDecodeMediaPlaylist(b *testing.B) {
}
}
}

func TestDecodeMediaPlaylistWithIndependentSegments(t *testing.T) {
f, err := os.Open("sample-playlists/media-playlist-with-independent-segments.m3u8")
if err != nil {
t.Fatal(err)
}

p, listType, err := DecodeFrom(bufio.NewReader(f), true)
if err != nil {
t.Fatal(err)
}
pp := p.(*MediaPlaylist)
CheckType(t, pp)
if listType != MEDIA {
t.Error("Sample not recognized as media playlist.")
}
err = pp.DecodeFrom(bufio.NewReader(f), false)
if err != nil {
t.Fatal(err)
}
if !pp.IndependentSegments() {
t.Error("Expected independent segments to be true")
}
}

func TestDecodeMediaPlaylistWithoutIndependentSegments(t *testing.T) {
f, err := os.Open("sample-playlists/media-playlist-with-gap.m3u8")
if err != nil {
t.Fatal(err)
}

p, listType, err := DecodeFrom(bufio.NewReader(f), true)
if err != nil {
t.Fatal(err)
}
pp := p.(*MediaPlaylist)
CheckType(t, pp)
if listType != MEDIA {
t.Error("Sample not recognized as media playlist.")
}
err = pp.DecodeFrom(bufio.NewReader(f), false)
if err != nil {
t.Fatal(err)
}
if pp.IndependentSegments() {
t.Error("Expected independentsegments to be false")
}
}

func TestWriteMediaPlaylistWithoutIndependentSegments(t *testing.T) {
f, err := os.Open("sample-playlists/media-playlist-with-gap.m3u8")
if err != nil {
t.Fatal(err)
}

p, listType, err := DecodeFrom(bufio.NewReader(f), true)
if err != nil {
t.Fatal(err)
}
pp := p.(*MediaPlaylist)
CheckType(t, pp)
if listType != MEDIA {
t.Error("Sample not recognized as media playlist.")
}
err = pp.DecodeFrom(bufio.NewReader(f), false)
if err != nil {
t.Fatal(err)
}
if pp.IndependentSegments() {
t.Error("Expected independentsegments to be false")
}
}
26 changes: 26 additions & 0 deletions sample-playlists/media-playlist-with-independent-segments.m3u8
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#EXTM3U
#EXT-X-VERSION:4
#EXT-X-TARGETDURATION:10
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-INDEPENDENT-SEGMENTS

#EXTINF:10.000,
0.ts
#EXTINF:10.000,
1.ts
#EXTINF:10.000,
2.ts
#EXTINF:10.000,
3.ts
#EXTINF:10.000,
4.ts
#EXTINF:10.000,
5.ts
#EXTINF:10.000,
6.ts
#EXTINF:10.000,
7.ts
#EXTINF:10.000,
8.ts
#EXTINF:10.000,
9.ts
49 changes: 25 additions & 24 deletions structure.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,30 +106,31 @@ const (
// #EXTINF:7.975,
// https://priv.example.com/fileSequence2682.ts
type MediaPlaylist struct {
TargetDuration float64
SeqNo uint64 // EXT-X-MEDIA-SEQUENCE
Segments []*MediaSegment
Args string // optional arguments placed after URIs (URI?Args)
Iframe bool // EXT-X-I-FRAMES-ONLY
Closed bool // is this VOD (closed) or Live (sliding) playlist?
MediaType MediaType
DiscontinuitySeq uint64 // EXT-X-DISCONTINUITY-SEQUENCE
StartTime float64
StartTimePrecise bool
durationAsInt bool // output durations as integers of floats?
keyformat int
winsize uint // max number of segments displayed in an encoded playlist; need set to zero for VOD playlists
capacity uint // total capacity of slice used for the playlist
head uint // head of FIFO, we add segments to head
tail uint // tail of FIFO, we remove segments from tail
count uint // number of segments added to the playlist
buf bytes.Buffer
ver uint8
Key *Key // EXT-X-KEY is optional encryption key displayed before any segments (default key for the playlist)
Map *Map // EXT-X-MAP is optional tag specifies how to obtain the Media Initialization Section (default map for the playlist)
WV *WV // Widevine related tags outside of M3U8 specs
Custom map[string]CustomTag
customDecoders []CustomDecoder
TargetDuration float64
SeqNo uint64 // EXT-X-MEDIA-SEQUENCE
Segments []*MediaSegment
Args string // optional arguments placed after URIs (URI?Args)
Iframe bool // EXT-X-I-FRAMES-ONLY
Closed bool // is this VOD (closed) or Live (sliding) playlist?
MediaType MediaType
DiscontinuitySeq uint64 // EXT-X-DISCONTINUITY-SEQUENCE
StartTime float64
StartTimePrecise bool
durationAsInt bool // output durations as integers of floats?
keyformat int
winsize uint // max number of segments displayed in an encoded playlist; need set to zero for VOD playlists
capacity uint // total capacity of slice used for the playlist
head uint // head of FIFO, we add segments to head
tail uint // tail of FIFO, we remove segments from tail
count uint // number of segments added to the playlist
buf bytes.Buffer
ver uint8
independentSegments bool
Key *Key // EXT-X-KEY is optional encryption key displayed before any segments (default key for the playlist)
Map *Map // EXT-X-MAP is optional tag specifies how to obtain the Media Initialization Section (default map for the playlist)
WV *WV // Widevine related tags outside of M3U8 specs
Custom map[string]CustomTag
customDecoders []CustomDecoder
}

// MasterPlaylist structure represents a master playlist which
Expand Down
16 changes: 16 additions & 0 deletions writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,18 @@ func (p *MediaPlaylist) ResetCache() {
p.buf.Reset()
}

// IndependentSegments returns true if all media samples in a segment can be
// decoded without information from other segments.
func (p *MediaPlaylist) IndependentSegments() bool {
return p.independentSegments
}

// SetIndependentSegments sets whether all media samples in a segment can be
// decoded without information from other segments.
func (p *MediaPlaylist) SetIndependentSegments(b bool) {
p.independentSegments = b
}

// Encode generates output in M3U8 format. Marshal `winsize` elements
// from bottom of the `segments` queue.
func (p *MediaPlaylist) Encode() *bytes.Buffer {
Expand All @@ -416,6 +428,10 @@ func (p *MediaPlaylist) Encode() *bytes.Buffer {
p.buf.WriteString(strver(p.ver))
p.buf.WriteRune('\n')

if p.IndependentSegments() {
p.buf.WriteString("#EXT-X-INDEPENDENT-SEGMENTS\n")
}

// Write any custom master tags
if p.Custom != nil {
for _, v := range p.Custom {
Expand Down
59 changes: 59 additions & 0 deletions writer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1223,3 +1223,62 @@ func BenchmarkEncodeMediaPlaylist(b *testing.B) {
_ = p.Encode() // disregard output
}
}

func TestMediaPlaylistIndependentSegments(t *testing.T) {
p, _ := NewMediaPlaylist(3, 5)
for i := 0; i < 5; i++ {
p.Append(fmt.Sprintf("test%d.ts", i), 5.0, "")
}
if p.IndependentSegments() {
t.Errorf("Expected independent segments to be false by default")
}
p.SetIndependentSegments(true)
if !p.IndependentSegments() {
t.Errorf("Expected independent segments to be true")
}
if !strings.Contains(p.Encode().String(), "#EXT-X-INDEPENDENT-SEGMENTS") {
t.Error("Expected playlist to contain EXT-X-INDEPENDENT-SEGMENTS tag")
}
fmt.Print(p)
// Output:
// #EXTM3U
// #EXT-X-VERSION:3
// #EXT-X-INDEPENDENT-SEGMENTS
// #EXT-X-MEDIA-SEQUENCE:0
// #EXT-X-TARGETDURATION:5
// #EXTINF:5.000,
// test0.ts
// #EXTINF:5.000,
// test1.ts
// #EXTINF:5.000,
// test2.ts
}

func TestMediaPlaylistWithoutIndependentSegments(t *testing.T) {
p, _ := NewMediaPlaylist(3, 5)
for i := 0; i < 5; i++ {
p.Append(fmt.Sprintf("test%d.ts", i), 5.0, "")
}
if p.IndependentSegments() != false {
t.Errorf("Expected independent segments to be false by default")
}
p.SetIndependentSegments(false)
if p.IndependentSegments() {
t.Errorf("Expected independent segments to be false")
}
if strings.Contains(p.Encode().String(), "#EXT-X-INDEPENDENT-SEGMENTS") {
t.Error("Expected playlist shouldn't contain EXT-X-INDEPENDENT-SEGMENTS tag")
}
fmt.Print(p)
// Output:
// #EXTM3U
// #EXT-X-VERSION:3
// #EXT-X-MEDIA-SEQUENCE:0
// #EXT-X-TARGETDURATION:5
// #EXTINF:5.000,
// test0.ts
// #EXTINF:5.000,
// test1.ts
// #EXTINF:5.000,
// test2.ts
}

0 comments on commit c08fb32

Please sign in to comment.