From 9b14d00b1e9b69f48bdd0bf156c123084e6ad9ae Mon Sep 17 00:00:00 2001 From: Eric Lindvall Date: Sun, 14 Apr 2019 21:19:55 -0700 Subject: [PATCH] Add support for EXT-X-START --- reader.go | 14 ++++++++++++++ reader_test.go | 19 +++++++++++++++++++ structure.go | 4 +++- writer.go | 8 ++++++++ writer_test.go | 15 +++++++++++++++ 5 files changed, 59 insertions(+), 1 deletion(-) diff --git a/reader.go b/reader.go index 14c1f899..0b449fc0 100644 --- a/reader.go +++ b/reader.go @@ -490,6 +490,20 @@ func decodeLineOfMediaPlaylist(p *MediaPlaylist, wv *WV, state *decodingState, l if _, err = fmt.Sscanf(line, "#EXT-X-DISCONTINUITY-SEQUENCE:%d", &p.DiscontinuitySeq); strict && err != nil { return err } + case strings.HasPrefix(line, "#EXT-X-START:"): + state.listType = MEDIA + for k, v := range decodeParamsLine(line[13:]) { + switch k { + case "TIME-OFFSET": + st, err := strconv.ParseFloat(v, 64) + if err != nil { + return fmt.Errorf("Invalid TIME-OFFSET: %s: %v", v, err) + } + p.StartTime = st + case "PRECISE": + p.StartTimePrecise = v == "YES" + } + } case strings.HasPrefix(line, "#EXT-X-KEY:"): state.listType = MEDIA state.xkey = new(Key) diff --git a/reader_test.go b/reader_test.go index 4b491635..230badbf 100644 --- a/reader_test.go +++ b/reader_test.go @@ -665,6 +665,25 @@ func TestDecodeMediaPlaylistWithProgramDateTime(t *testing.T) { } } +func TestDecodeMediaPlaylistStartTime(t *testing.T) { + f, err := os.Open("sample-playlists/media-playlist-with-start-time.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.") + } + if pp.StartTime != float64(8.0) { + t.Errorf("Media segment StartTime != 8: %f", pp.StartTime) + } +} + /**************** * Benchmarks * ****************/ diff --git a/structure.go b/structure.go index 06e77408..7bd0ac2f 100644 --- a/structure.go +++ b/structure.go @@ -110,7 +110,9 @@ type MediaPlaylist struct { Closed bool // is this VOD (closed) or Live (sliding) playlist? MediaType MediaType DiscontinuitySeq uint64 // EXT-X-DISCONTINUITY-SEQUENCE - durationAsInt bool // output durations as integers of floats? + 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 diff --git a/writer.go b/writer.go index c27c0d25..236b071b 100644 --- a/writer.go +++ b/writer.go @@ -418,6 +418,14 @@ func (p *MediaPlaylist) Encode() *bytes.Buffer { p.buf.WriteString("#EXT-X-TARGETDURATION:") p.buf.WriteString(strconv.FormatInt(int64(math.Ceil(p.TargetDuration)), 10)) // due section 3.4.2 of M3U8 specs EXT-X-TARGETDURATION must be integer p.buf.WriteRune('\n') + if p.StartTime > 0.0 { + p.buf.WriteString("#EXT-X-START:TIME-OFFSET=") + p.buf.WriteString(strconv.FormatFloat(p.StartTime, 'f', -1, 64)) + if p.StartTimePrecise { + p.buf.WriteString(",PRECISE=YES") + } + p.buf.WriteRune('\n') + } if p.DiscontinuitySeq != 0 { p.buf.WriteString("#EXT-X-DISCONTINUITY-SEQUENCE:") p.buf.WriteString(strconv.FormatUint(uint64(p.DiscontinuitySeq), 10)) diff --git a/writer_test.go b/writer_test.go index a77c67a1..9784b290 100644 --- a/writer_test.go +++ b/writer_test.go @@ -614,6 +614,21 @@ func TestIndependentSegments(t *testing.T) { } } +// Create new media playlist +// Set default map +func TestStartTimeOffset(t *testing.T) { + p, e := NewMediaPlaylist(3, 5) + if e != nil { + t.Fatalf("Create media playlist failed: %s", e) + } + p.StartTime = 3.4 + + expected := `#EXT-X-START:TIME-OFFSET=3.4` + if !strings.Contains(p.String(), expected) { + t.Fatalf("Media playlist did not contain: %s\nMedia Playlist:\n%v", expected, p.String()) + } +} + func TestMediaPlaylist_Slide(t *testing.T) { m, e := NewMediaPlaylist(3, 4) if e != nil {