Skip to content

Commit

Permalink
improve playlist parser
Browse files Browse the repository at this point in the history
  • Loading branch information
aler9 committed Oct 2, 2024
1 parent 81e3dd9 commit 7fb3e8c
Show file tree
Hide file tree
Showing 8 changed files with 126 additions and 101 deletions.
4 changes: 2 additions & 2 deletions client_primary_downloader.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,8 +222,8 @@ func (d *clientPrimaryDownloader) run(ctx context.Context) error {
return fmt.Errorf("audio playlist with id \"%s\" not found", leadingPlaylist.Audio)
}

if audioPlaylist.URI != "" {
u, err = clientAbsoluteURL(d.primaryPlaylistURL, audioPlaylist.URI)
if audioPlaylist.URI != nil {
u, err = clientAbsoluteURL(d.primaryPlaylistURL, *audioPlaylist.URI)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion muxer_stream.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ func (s *muxerStream) populateMultivariantPlaylist(
r := &playlist.MultivariantRendition{
Type: playlist.MultivariantRenditionTypeAudio,
GroupID: "audio",
URI: uri,
URI: &uri,

Check warning on line 162 in muxer_stream.go

View check run for this annotation

Codecov / codecov/patch

muxer_stream.go#L162

Added line #L162 was not covered by tests
}
pl.Renditions = append(pl.Renditions, r)
}
Expand Down
39 changes: 21 additions & 18 deletions pkg/playlist/media.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,57 +42,57 @@ const (

// Media is a media playlist.
type Media struct {
// #EXT-X-VERSION
// EXT-X-VERSION
// required
Version int

// #EXT-X-INDEPENDENT-SEGMENTS
// EXT-X-INDEPENDENT-SEGMENTS
IndependentSegments bool

// #EXT-X-START
// EXT-X-START
Start *MediaStart

// #EXT-X-ALLOWCACHE
// EXT-X-ALLOWCACHE
// removed since v7
AllowCache *bool

// #EXT-X-TARGETDURATION
// EXT-X-TARGETDURATION
// required
TargetDuration int

// #EXT-X-SERVER-CONTROL
// EXT-X-SERVER-CONTROL
ServerControl *MediaServerControl

// #EXT-X-PART-INF
// EXT-X-PART-INF
PartInf *MediaPartInf

// #EXT-X-MEDIA-SEQUENCE
// EXT-X-MEDIA-SEQUENCE
// required
MediaSequence int

// #EXT-X-DISCONTINUITY-SEQUENCE
// EXT-X-DISCONTINUITY-SEQUENCE
DiscontinuitySequence *int

// #EXT-X-PLAYLIST-TYPE
// EXT-X-PLAYLIST-TYPE
PlaylistType *MediaPlaylistType

// #EXT-X-MAP
// EXT-X-MAP
Map *MediaMap

// #EXT-X-SKIP
// EXT-X-SKIP
Skip *MediaSkip

// segments
// at least one is required
Segments []*MediaSegment

// #EXT-X-PART
// EXT-X-PART
Parts []*MediaPart

// #EXT-X-PRELOAD-HINT
// EXT-X-PRELOAD-HINT
PreloadHint *MediaPreloadHint

// #EXT-X-ENDLIST
// EXT-X-ENDLIST
Endlist bool
}

Expand Down Expand Up @@ -233,6 +233,12 @@ func (m *Media) Unmarshal(buf []byte) error {
return err
}

case line == "#EXT-X-DISCONTINUITY":
curSegment.Discontinuity = true

case line == "#EXT-X-GAP":
curSegment.Gap = true

case strings.HasPrefix(line, "#EXT-X-PROGRAM-DATE-TIME:"):
line = line[len("#EXT-X-PROGRAM-DATE-TIME:"):]

Expand All @@ -244,9 +250,6 @@ func (m *Media) Unmarshal(buf []byte) error {

curSegment.DateTime = &tmp

case line == "#EXT-X-GAP":
curSegment.Gap = true

case strings.HasPrefix(line, "#EXT-X-BITRATE:"):
line = line[len("#EXT-X-BITRATE:"):]

Expand Down
29 changes: 18 additions & 11 deletions pkg/playlist/media_segment.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,32 @@ import (

// MediaSegment is a segment of a media playlist.
type MediaSegment struct {
// #EXTINF
// EXTINF
// required
Duration time.Duration
Title string

// segment URI.
// URI.
// required
URI string

// #EXT-X-PROGRAM-DATE-TIME
DateTime *time.Time // optional
// EXT-X-DISCONTINUITY
Discontinuity bool

// #EXT-X-GAP
Gap bool // optional
// EXT-X-GAP
Gap bool

// #EXT-X-BITRATE
// EXT-X-PROGRAM-DATE-TIME
DateTime *time.Time

// EXT-X-BITRATE
Bitrate *int

// #EXT-X-BYTERANGE
// EXT-X-BYTERANGE
ByteRangeLength *uint64
ByteRangeStart *uint64

// #EXT-X-PART
// EXT-X-PART
Parts []*MediaPart
}

Expand All @@ -50,14 +53,18 @@ func (s MediaSegment) validate() error {
func (s MediaSegment) marshal() string {
ret := ""

if s.DateTime != nil {
ret += "#EXT-X-PROGRAM-DATE-TIME:" + s.DateTime.Format(timeRFC3339Millis) + "\n"
if s.Discontinuity {
ret += "#EXT-X-DISCONTINUITY\n"
}

if s.Gap {
ret += "#EXT-X-GAP\n"
}

if s.DateTime != nil {
ret += "#EXT-X-PROGRAM-DATE-TIME:" + s.DateTime.Format(timeRFC3339Millis) + "\n"
}

if s.Bitrate != nil {
ret += "#EXT-X-BITRATE:" + strconv.FormatInt(int64(*s.Bitrate), 10) + "\n"
}
Expand Down
11 changes: 7 additions & 4 deletions pkg/playlist/media_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ var casesMedia = []struct {
"#EXT-X-PROGRAM-DATE-TIME:2014-08-25T00:00:00Z\n" +
"#EXTINF:2.00000,\n" +
"seg1.mp4\n" +
"#EXT-X-DISCONTINUITY\n" +
"#EXT-X-PROGRAM-DATE-TIME:2014-08-25T00:00:00Z\n" +
"#EXT-X-BITRATE:14213213\n" +
"#EXT-X-PART:DURATION=1.50000,URI=\"part1.mp4\",INDEPENDENT=YES\n" +
Expand All @@ -60,6 +61,7 @@ var casesMedia = []struct {
"#EXT-X-PROGRAM-DATE-TIME:2014-08-25T00:00:00Z\n" +
"#EXTINF:2.00000,\n" +
"seg1.mp4\n" +
"#EXT-X-DISCONTINUITY\n" +
"#EXT-X-PROGRAM-DATE-TIME:2014-08-25T00:00:00Z\n" +
"#EXT-X-BITRATE:14213213\n" +
"#EXT-X-PART:DURATION=1.50000,URI=\"part1.mp4\",INDEPENDENT=YES\n" +
Expand Down Expand Up @@ -101,10 +103,11 @@ var casesMedia = []struct {
URI: "seg1.mp4",
},
{
DateTime: timePtr(time.Date(2014, 8, 25, 0, 0, 0, 0, time.UTC)),
Bitrate: intPtr(14213213),
Duration: 3 * time.Second,
URI: "seg2.mp4",
DateTime: timePtr(time.Date(2014, 8, 25, 0, 0, 0, 0, time.UTC)),
Bitrate: intPtr(14213213),
Duration: 3 * time.Second,
URI: "seg2.mp4",
Discontinuity: true,
Parts: []*MediaPart{
{
Duration: 1500 * time.Millisecond,
Expand Down
10 changes: 5 additions & 5 deletions pkg/playlist/multivariant.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,21 @@ import (

// Multivariant is a multivariant playlist.
type Multivariant struct {
// #EXT-X-VERSION
// EXT-X-VERSION
// required
Version int

// #EXT-X-INDEPENDENT-SEGMENTS
// EXT-X-INDEPENDENT-SEGMENTS
IndependentSegments bool

// #EXT-X-START
// EXT-X-START
Start *MultivariantStart

// #EXT-X-STREAM-INF
// EXT-X-STREAM-INF
// at least one is required
Variants []*MultivariantVariant

// #EXT-X-MEDIA
// EXT-X-MEDIA
Renditions []*MultivariantRendition
}

Expand Down
80 changes: 44 additions & 36 deletions pkg/playlist/multivariant_rendition.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,32 +27,33 @@ type MultivariantRendition struct {
// required
GroupID string

// URI
// required for all types except CLOSED-CAPTIONS
URI string

// INSTREAM-ID
// required for CLOSED-CAPTIONS
InstreamID string

// NAME
// required
Name string

// LANGUAGE
Language string

// DEFAULT
Default bool

// AUTOSELECT
Autoselect bool

// DEFAULT
Default bool

// FORCED
Forced *bool
Forced bool

// CHANNELS
Channels string
// for AUDIO only
Channels *string

// URI
// must not be present for CLOSED-CAPTIONS
URI *string

// INSTREAM-ID
// for CLOSED-CAPTIONS only
InStreamID *string
}

func (t *MultivariantRendition) unmarshal(v string) error {
Expand Down Expand Up @@ -89,17 +90,19 @@ func (t *MultivariantRendition) unmarshal(v string) error {
t.Autoselect = (val == "YES")

case "FORCED":
v := (val == "YES")
t.Forced = &v
t.Forced = (val == "YES")

case "CHANNELS":
t.Channels = val
v := val
t.Channels = &v

case "URI":
t.URI = val
v := val
t.URI = &v

case "INSTREAM-ID":
t.InstreamID = val
v := val
t.InStreamID = &v
}
}

Expand All @@ -117,12 +120,12 @@ func (t *MultivariantRendition) unmarshal(v string) error {
// type is SUBTITLES, but OPTIONAL if the media type is VIDEO or AUDIO.
switch t.Type {
case MultivariantRenditionTypeClosedCaptions:
if t.URI != "" {
if t.URI != nil {
return fmt.Errorf("URI is forbidden for type CLOSED-CAPTIONS")
}

case MultivariantRenditionTypeSubtitles:
if t.URI == "" {
if t.URI == nil {
return fmt.Errorf("URI is required for type SUBTITLES")
}

Expand All @@ -132,11 +135,17 @@ func (t *MultivariantRendition) unmarshal(v string) error {
// This attribute is REQUIRED if the TYPE attribute is CLOSED-CAPTIONS
// For all other TYPE values, the INSTREAM-ID MUST NOT be specified.
if t.Type == MultivariantRenditionTypeClosedCaptions {
if t.InstreamID == "" {
if t.InStreamID == nil {
return fmt.Errorf("missing INSTREAM-ID")
}
} else if t.InstreamID != "" {
return fmt.Errorf("INSTREAM-ID is forbidden with type %s", t.Type)
} else if t.InStreamID != nil {
return fmt.Errorf("INSTREAM-ID is forbidden for type %s", t.Type)
}

// The CHANNELS attribute MUST NOT be present unless the TYPE is
// AUDIO.
if t.Channels != nil && t.Type != MultivariantRenditionTypeAudio {
return fmt.Errorf("CHANNELS is forbidden for type %s", t.Type)

Check warning on line 148 in pkg/playlist/multivariant_rendition.go

View check run for this annotation

Codecov / codecov/patch

pkg/playlist/multivariant_rendition.go#L148

Added line #L148 was not covered by tests
}

return nil
Expand All @@ -153,29 +162,28 @@ func (t MultivariantRendition) marshal() string {
ret += ",NAME=\"" + t.Name + "\""
}

if t.Autoselect {
ret += ",AUTOSELECT=YES"
}

if t.Default {
ret += ",DEFAULT=YES"
}

if t.Autoselect {
ret += ",AUTOSELECT=YES"
if t.Forced {
ret += ",FORCED=YES"

Check warning on line 174 in pkg/playlist/multivariant_rendition.go

View check run for this annotation

Codecov / codecov/patch

pkg/playlist/multivariant_rendition.go#L174

Added line #L174 was not covered by tests
}

if t.Forced != nil {
ret += ",FORCED="
if *t.Forced {
ret += "YES"
} else {
ret += "NO"
}
if t.Channels != nil {
ret += ",CHANNELS=\"" + *t.Channels + "\""
}

if t.Channels != "" {
ret += ",CHANNELS=\"" + t.Channels + "\""
if t.URI != nil {
ret += ",URI=\"" + *t.URI + "\""
}

if t.URI != "" {
ret += ",URI=\"" + t.URI + "\""
if t.InStreamID != nil {
ret += ",INSTREAM-ID=\"" + *t.InStreamID + "\""
}

ret += "\n"
Expand Down
Loading

0 comments on commit 7fb3e8c

Please sign in to comment.