streaming

Media streaming and broadcast systems in Go
Log | Files | Refs | README | LICENSE

commit 585a7c087730c7083c4a5fe80e02575561682dd1
parent b3fe201d0ff0f46894b72a43318de23db09c3acc
Author: Oliver Lowe <o@olowe.co>
Date:   Thu, 23 May 2024 20:28:12 +1000

m3u8: Handle writing more attributes of Variants

Involves a correction of how we stored closed caption information.
Reading and writing the Tears of Steel playlist from
test-streams.mux.dev now works ok!

Diffstat:
Mm3u8/m3u8.go | 11+++++++++--
Mm3u8/parse.go | 5+++--
Mm3u8/parse_test.go | 25++-----------------------
Dm3u8/testdata/closed_captions.m3u8 | 6------
Mm3u8/write.go | 18++++++++++++++++++
5 files changed, 32 insertions(+), 33 deletions(-)

diff --git a/m3u8/m3u8.go b/m3u8/m3u8.go @@ -217,9 +217,16 @@ type Variant struct { Audio string Video string Subtitles string - ClosedCaptions []string // `NONE` or comma-separated values + // May be NoClosedCaptions or the empty string to indicate + // absence of closed captions. + ClosedCaptions string } +// NoClosedCaptions may be the value for Variant.ClosedCaptions to +// explicitly indicate that no closed captions are available for the +// Variant. +const NoClosedCaptions string = "NONE" + type HDCPLevel uint8 const ( @@ -228,7 +235,7 @@ const ( HDCPType1 ) -func (l HDCPLevel) String() { +func (l HDCPLevel) String() string { switch l { case HDCPNone: return "NONE" diff --git a/m3u8/parse.go b/m3u8/parse.go @@ -154,11 +154,12 @@ func parseVariant(items chan item) (*Variant, error) { } v.HDCP = l case "AUDIO", "VIDEO", "SUBTITLES": - name := it.val + name := attr.val it = <-items if it.typ != itemString { return nil, fmt.Errorf("parse %s: unexpected %s", name, it) } + it.val = strings.Trim(it.val, `"`) if name == "AUDIO" { v.Audio = it.val } else if name == "VIDEO" { @@ -171,7 +172,7 @@ func parseVariant(items chan item) (*Variant, error) { if it.typ != itemString { return nil, fmt.Errorf("parse closed-captions: unexpcted %s", it) } - v.ClosedCaptions = strings.Split(strings.Trim(it.val, `"`), ",") + v.ClosedCaptions = strings.Trim(it.val, `"`) default: return nil, fmt.Errorf("unknown attribute %s", attr.val) } diff --git a/m3u8/parse_test.go b/m3u8/parse_test.go @@ -4,7 +4,6 @@ import ( "os" "path" "path/filepath" - "reflect" "testing" "time" ) @@ -21,7 +20,8 @@ func TestParse(t *testing.T) { t.Fatal(err) } defer f.Close() - if _, err := ParsePlaylist(f); err != nil { + _, err = ParsePlaylist(f) + if err != nil { t.Fatal(err) } }) @@ -66,27 +66,6 @@ func TestParseByteRange(t *testing.T) { } } -func TestParseClosedCaptions(t *testing.T) { - f, err := os.Open("testdata/closed_captions.m3u8") - if err != nil { - t.Fatal(err) - } - defer f.Close() - p, err := ParsePlaylist(f) - if err != nil { - t.Fatal(err) - } - cc := p.Variants[0].ClosedCaptions[0] - if cc != "NONE" { - t.Errorf("want closed captions %q, got %s", "NONE", cc) - } - cc2 := p.Variants[1].ClosedCaptions - want := []string{"something", "another"} - if !reflect.DeepEqual(want, cc2) { - t.Errorf("want closed captions %s, got %s", want, cc2) - } -} - // Tests that we parse floats and integers of different precisions ok. func TestFrameRate(t *testing.T) { f, err := os.Open("testdata/frame_rate.m3u8") diff --git a/m3u8/testdata/closed_captions.m3u8 b/m3u8/testdata/closed_captions.m3u8 @@ -1,6 +0,0 @@ -#EXTM3U -#EXT-X-STREAM-INF:CLOSED-CAPTIONS=NONE -tos_1080p/index.m3u8 -## can also have quoted strings, comma-delimeted -#EXT-X-STREAM-INF:CLOSED-CAPTIONS="something,another" -playlist.m3u8 diff --git a/m3u8/write.go b/m3u8/write.go @@ -107,6 +107,24 @@ func Encode(w io.Writer, p *Playlist) error { if v.Resolution != [2]int{0, 0} { fmt.Fprintf(w, "RESOLUTION=%dx%d,", v.Resolution[0], v.Resolution[1]) } + if v.FrameRate > 0 { + fmt.Fprintf(w, "FRAME-RATE=%f,", v.FrameRate) + } + if v.HDCP != HDCPNone { + fmt.Fprintf(w, "HDCP-LEVEL=%s,", v.HDCP) + } + if v.Audio != "" { + fmt.Fprintf(w, "AUDIO=%q,", v.Audio) + } + if v.Video != "" { + fmt.Fprintf(w, "VIDEO=%q,", v.Video) + } + if v.Subtitles != "" { + fmt.Fprintf(w, "SUBTITLES=%q,", v.Subtitles) + } + if v.ClosedCaptions != "" && v.ClosedCaptions != NoClosedCaptions { + fmt.Fprintf(w, "CLOSED-CAPTIONS=%q,", v.ClosedCaptions) + } fmt.Fprintln(w) fmt.Fprintln(w, v.URI) }