commit 6f91fd7e149c0466f57b4288c77d2b2c0c35de78
parent c0a87bbed2f0947fe30ae80a5466bdae4de7621b
Author: blackfalcon <blackfalcon.ru@gmail.com>
Date: Thu, 30 May 2024 14:03:17 -0300
m3u8: support writing SessionData
Resolves #8
Diffstat:
4 files changed, 99 insertions(+), 3 deletions(-)
diff --git a/m3u8/m3u8.go b/m3u8/m3u8.go
@@ -283,8 +283,23 @@ type IFrameInfo Variant
// SessionData represents the EXT-X-SESSION-DATA tag.
type SessionData struct {
- ID string
- Value string
+ ID string // This attribute is REQUIRED
+ Value string // MUST contain either a VALUE or URI attribute, but not both
URI string
- Language string
+ Language string // This attribute is OPTIONAL.
+}
+
+func (sd SessionData) String() string {
+ var attrs []string
+ attrs = append(attrs, fmt.Sprintf("DATA-ID=%q", sd.ID))
+ if sd.Value != "" {
+ attrs = append(attrs, fmt.Sprintf("VALUE=%q", sd.Value))
+ }
+ if sd.URI != "" {
+ attrs = append(attrs, fmt.Sprintf("URI=%q", sd.URI))
+ }
+ if sd.Language != "" {
+ attrs = append(attrs, fmt.Sprintf("LANGUAGE=%q", sd.Language))
+ }
+ return strings.Join(attrs, ",")
}
diff --git a/m3u8/m3u8_test.go b/m3u8/m3u8_test.go
@@ -0,0 +1,64 @@
+package m3u8
+
+import (
+ "bytes"
+ "strings"
+ "testing"
+)
+
+func TestWriteSessionData(t *testing.T) {
+ var cases = []struct {
+ name string
+ sd SessionData
+ want string
+ valid bool
+ }{
+ {
+ "all attributes set",
+ SessionData{ID: "1234", Value: "5678", Language: "indonesian"},
+ `#EXT-X-SESSION-DATA:DATA-ID="1234",VALUE="5678",LANGUAGE="indonesian"`,
+ true,
+ },
+ {
+ "language is optional",
+ SessionData{ID: "1234", URI: "hello/hi.json"},
+ `#EXT-X-SESSION-DATA:DATA-ID="1234",URI="hello/hi.json"`,
+ true,
+ },
+ {
+ "required attributes set(id, value or uri)",
+ SessionData{ID: "1234", Value: "5678"},
+ `#EXT-X-SESSION-DATA:DATA-ID="1234",VALUE="5678"`,
+ true,
+ },
+ {
+ "empty id",
+ SessionData{URI: "hello/hi.json"},
+ "",
+ false,
+ },
+ {
+ "both URI and Value set",
+ SessionData{ID: "1234", URI: "hello/hi.json", Value: "5678"},
+ "",
+ false,
+ },
+ }
+ for _, tt := range cases {
+ t.Run(tt.name, func(t *testing.T) {
+ buf := &bytes.Buffer{}
+ _, err := writeSessionData(buf, tt.sd)
+ if err == nil && !tt.valid {
+ t.Fatalf("nil error on invalid session data %v", tt.sd)
+ } else if err != nil && tt.valid {
+ t.Fatalf("non-nil error %v on valid session data", err)
+ }
+ got := strings.TrimSpace(buf.String()) // strip newlines
+ if got != tt.want {
+ t.Errorf("unexpected session data text")
+ t.Log("got:", got)
+ t.Log("want:", tt.want)
+ }
+ })
+ }
+}
diff --git a/m3u8/parse.go b/m3u8/parse.go
@@ -19,6 +19,7 @@ const (
tagMediaSequence = "#EXT-X-MEDIA-SEQUENCE" // RFC 8216, 4.3.3.2
tagEndList = "#EXT-X-ENDLIST" // RFC 8216, 4.4.3.4
tagIndependentSegments = "#EXT-X-INDEPENDENT-SEGMENTS" // RFC 8216, 4.3.5.1
+ tagSessionData = "#EXT-X-SESSION-DATA" // RFC 8216, 4.3.4.4
)
func ParsePlaylist(rd io.Reader) (*Playlist, error) {
diff --git a/m3u8/write.go b/m3u8/write.go
@@ -120,6 +120,12 @@ func Encode(w io.Writer, p *Playlist) error {
fmt.Fprintln(w, v.URI)
}
+ for i, sd := range p.SessionData {
+ if _, err := writeSessionData(w, sd); err != nil {
+ return fmt.Errorf("write session data %d: %w", i, err)
+ }
+ }
+
if p.End {
fmt.Fprintln(w, tagEndList)
}
@@ -173,3 +179,13 @@ func writeMap(w io.Writer, m Map) (n int, err error) {
}
return fmt.Fprintf(w, "%s:URI=%q\n", tagMap, m.URI)
}
+
+func writeSessionData(w io.Writer, sd SessionData) (n int, err error) {
+ if sd.ID == "" {
+ return 0, fmt.Errorf("ID not set")
+ }
+ if sd.URI != "" && sd.Value != "" {
+ return 0, fmt.Errorf("only one of Value or URI may be set")
+ }
+ return fmt.Fprintf(w, "%s:%s\n", tagSessionData, sd)
+}