Skip to content

Commit

Permalink
feat: support #EXT-X-PART-INF:PART-TARGET tag
Browse files Browse the repository at this point in the history
  • Loading branch information
Wkkkkk committed Feb 10, 2025
1 parent 6f2d765 commit a5b9892
Show file tree
Hide file tree
Showing 7 changed files with 75 additions and 1 deletion.
1 change: 1 addition & 0 deletions m3u8/read_write_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,7 @@ func TestReadWritePlaylists(t *testing.T) {
"media-playlist-with-multiple-dateranges.m3u8",
"media-playlist-with-start-time.m3u8",
"master-with-independent-segments.m3u8",
// "media-playlist-low-latency.m3u8",
}

for _, fileName := range files {
Expand Down
5 changes: 5 additions & 0 deletions m3u8/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -874,6 +874,11 @@ func decodeLineOfMediaPlaylist(p *MediaPlaylist, state *decodingState, line stri
if _, err = fmt.Sscanf(line, "#EXT-X-TARGETDURATION:%d", &p.TargetDuration); strict && err != nil {
return err
}
case strings.HasPrefix(line, "#EXT-X-PART-INF:PART-TARGET="):
state.listType = MEDIA
if _, err = fmt.Sscanf(line, "#EXT-X-PART-INF:PART-TARGET=%f", &p.PartTargetDuration); strict && err != nil {
return err
}
case strings.HasPrefix(line, "#EXT-X-MEDIA-SEQUENCE:"):
state.listType = MEDIA
if _, err = fmt.Sscanf(line, "#EXT-X-MEDIA-SEQUENCE:%d", &p.SeqNo); strict && err != nil {
Expand Down
16 changes: 16 additions & 0 deletions m3u8/reader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -862,6 +862,22 @@ func TestMediaPlaylistWithSCTE35Tag(t *testing.T) {
}
}

func TestDecodeLowLatencyMediaPlaylist(t *testing.T) {
is := is.New(t)
f, err := os.Open("sample-playlists/media-playlist-low-latency.m3u8")
is.NoErr(err) // must open file
p, listType, err := DecodeFrom(bufio.NewReader(f), true)
is.NoErr(err) // must decode playlist
pp := p.(*MediaPlaylist)
CheckType(t, pp)
is.Equal(listType, MEDIA) // must be media playlist
// check parsed values
is.Equal(pp.TargetDuration, uint(4)) // target duration must be 15
is.True(!pp.Closed) // live playlist
is.Equal(pp.SeqNo, uint64(0)) // sequence number must be 0
is.Equal(pp.PartTargetDuration, float32(1.002000)) // part target duration must be 1.002000
}

func TestDecodeMediaPlaylistWithProgramDateTime(t *testing.T) {
is := is.New(t)
f, err := os.Open("sample-playlists/media-playlist-with-program-date-time.m3u8")
Expand Down
28 changes: 28 additions & 0 deletions m3u8/sample-playlists/media-playlist-low-latency.m3u8
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#EXTM3U
#EXT-X-TARGETDURATION:4
#EXT-X-VERSION:6
#EXT-X-PART-INF:PART-TARGET=1.002000
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-PROGRAM-DATE-TIME:2025-02-10T13:17:05.056Z
#EXT-X-MAP:URI="fileSequence0.mp4"
#EXTINF:4.00000,
fileSequence1.m4s
#EXTINF:4.00000,
fileSequence2.m4s
#EXTINF:4.00000,
fileSequence3.m4s
#EXT-X-PART:DURATION=1.00000,INDEPENDENT=YES,URI="filePart4.1.m4s"
#EXT-X-PART:DURATION=1.00000,INDEPENDENT=YES,URI="filePart4.2.m4s"
#EXT-X-PART:DURATION=1.00000,INDEPENDENT=YES,URI="filePart4.3.m4s"
#EXT-X-PART:DURATION=1.00000,INDEPENDENT=YES,URI="filePart4.4.m4s"
#EXTINF:4.00000,
fileSequence4.m4s
#EXT-X-PART:DURATION=1.00000,INDEPENDENT=YES,URI="filePart5.1.m4s"
#EXT-X-PART:DURATION=1.00000,INDEPENDENT=YES,URI="filePart5.2.m4s"
#EXT-X-PART:DURATION=1.00000,INDEPENDENT=YES,URI="filePart5.3.m4s"
#EXT-X-PART:DURATION=1.00000,INDEPENDENT=YES,URI="filePart5.4.m4s"
#EXTINF:4.00000,
fileSequence5.m4s
#EXT-X-PROGRAM-DATE-TIME:2025-02-10T13:17:25.056Z
#EXT-X-PART:DURATION=1.00000,INDEPENDENT=YES,URI="filePart6.1.m4s"
#EXT-X-PRELOAD-HINT:TYPE=PART,URI="filePart6.2.m4s"
2 changes: 1 addition & 1 deletion m3u8/structure.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ type MediaPlaylist struct {
ver uint8 // protocol version of the playlist, 3 or higher
targetDurLocked bool // target duration is locked and cannot be changed
independentSegments bool // Global tag for EXT-X-INDEPENDENT-SEGMENTS

PartTargetDuration float32 // EXT-X-PART-INF:PART-TARGET
}

// MasterPlaylist represents a master (multivariant) playlist which
Expand Down
5 changes: 5 additions & 0 deletions m3u8/writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,11 @@ func (p *MediaPlaylist) Encode() *bytes.Buffer {
p.buf.WriteString("VOD\n")
}
}
if p.PartTargetDuration > 0 {
p.buf.WriteString("#EXT-X-PART-INF:PART-TARGET=")
p.buf.WriteString(strconv.FormatFloat(float64(p.PartTargetDuration), 'f', 6, 64))
p.buf.WriteRune('\n')
}
p.buf.WriteString("#EXT-X-MEDIA-SEQUENCE:")
p.buf.WriteString(strconv.FormatUint(p.SeqNo, 10))
p.buf.WriteRune('\n')
Expand Down
19 changes: 19 additions & 0 deletions m3u8/writer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,25 @@ test01.ts
is.Equal(out, expected) // Encode media playlist does not match expected
}

func TestEncodeLowLatencyMediaPlaylist(t *testing.T) {
is := is.New(t)
p, e := NewMediaPlaylist(3, 5)
is.NoErr(e) // Create media playlist should be successful
p.PartTargetDuration = 1.002
e = p.Append("test01.ts", 5.0, "")
is.NoErr(e) // Add 1st segment to a media playlist should be successful
expected := `#EXTM3U
#EXT-X-VERSION:3
#EXT-X-PART-INF:PART-TARGET=1.002000
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-TARGETDURATION:5
#EXTINF:5.000,
test01.ts
`
out := p.String()
is.Equal(out, expected) // Encode media playlist does not match expected
}

// Create new media playlist
// Add 10 segments to media playlist
// Test iterating over segments
Expand Down

0 comments on commit a5b9892

Please sign in to comment.