commit e5a2561859aaf24e6368af6a4e82fc8112bc3d08 from: Oliver Lowe date: Sun May 4 04:02:40 2025 UTC m3u8: ignore trailing commas, support quoted string attributes Playlist from https://test-streams.mux.dev/dai-discontinuity-deltatre/manifest.m3u8 containing discontinuities exposed the bugs. commit - 3083e3dd1cb493efa7557138726614b1d3758e81 commit + e5a2561859aaf24e6368af6a4e82fc8112bc3d08 blob - e3d28a0f2d8b84a79679df36ff2b10219b4b703d blob + 72a79eb6300b633779d9890cb85f70b5f0cb7bc8 --- m3u8/lex.go +++ m3u8/lex.go @@ -127,6 +127,7 @@ func newLexer(r io.Reader) *lexer { return &lexer{ sc: bufio.NewScanner(r), items: make(chan item), + debug: false, } } @@ -135,7 +136,10 @@ func lexStart(l *lexer) stateFn { if l.sc.Text() == "" { continue // ignore blank lines } - l.input = l.sc.Text() + "\n" + text := l.sc.Text() + // ignore valid but meaningless trailing commas. + text = strings.TrimSuffix(text, ",") + l.input = text + "\n" l.pos = 0 l.start = 0 if strings.HasPrefix(l.input, tagStart) { @@ -145,7 +149,7 @@ func lexStart(l *lexer) stateFn { } // not a tag, so must be a URL. // emit the URL, then the newline we appended ourselves. - l.pos = len(l.sc.Text()) + l.pos = len(text) l.emit(itemURL) l.emit(itemNewline) } @@ -179,7 +183,7 @@ func lexTagName(l *lexer) stateFn { case ':': l.emit(itemTag) l.next() - l.ignore() + l.ignore() // ignore ':' after tag name return lexAttrs(l) } return l.errorf("illegal tag character %q", r) @@ -225,6 +229,9 @@ func lexAttrs(l *lexer) stateFn { return lexAttrValue(l) case r == '@': return lexAttrValue(l) + case r == '"': + l.next() + return lexQString(l) default: return l.errorf("illegal character %q in attribute name", r) } blob - /dev/null blob + 79e1a52b3948e69c1df6200c4fcc9cb9165588a3 (mode 644) --- /dev/null +++ m3u8/testdata/discontinuities.m3u8 @@ -0,0 +1,78 @@ +#EXTM3U +#EXT-X-YOSPACE-ANALYTICS-URL:"https://csm-e-cebteurxaws416-2hjvnjz85og7.tls1.yospace.com/csm/analytics;jsessionid=E97CB20FD3BCE93F89ADDB12E8C82895.csm-e-cebteurxaws416-2hjvnjz85og7.tls1.yospace.com" +#EXT-X-TARGETDURATION:10 +#EXT-X-VERSION:3 +#EXT-X-PLAYLIST-TYPE:EVENT +#EXT-X-KEY:METHOD=AES-128,URI="key1.json?f=1041&s=0&p=1822767&m=1506045858",IV=0x000000000000000000000000001BD02F +#EXTINF:10, +1041_6_1822767.ts?m=1506045858 +#EXT-X-KEY:METHOD=AES-128,URI="key2.json?f=1041&s=0&p=1822768&m=1506045858",IV=0x000000000000000000000000001BD030 +#EXTINF:10, +1041_6_1822768.ts?m=1506045858 +#EXT-X-KEY:METHOD=AES-128,URI="key3.json?f=1041&s=0&p=1822769&m=1506045858",IV=0x000000000000000000000000001BD031 +#EXTINF:10, +1041_6_1822769.ts?m=1506045858 +#EXT-X-KEY:METHOD=AES-128,URI="key4.json?f=1041&s=0&p=1822770&m=1506045858",IV=0x000000000000000000000000001BD032 +#EXTINF:6.28, +1041_6_1822770.ts?m=1506045858 +#EXT-X-DISCONTINUITY +#EXT-X-KEY:METHOD=NONE +#EXTINF:10, +u-6400-m-720x408-1628-a-96-1-1.ts +#EXTINF:10, +u-6400-m-720x408-1628-a-96-1-2.ts +#EXTINF:7.2, +u-6400-m-720x408-1628-a-96-1-3.ts +#EXTINF:2.8, +u-6400-m-720x408-1628-a-96-1-4.ts +#EXT-X-DISCONTINUITY +#EXTINF:10, +u-6400-m-720x408-1628-a-96-1-1-1.ts +#EXTINF:10, +u-6400-m-720x408-1628-a-96-1-2-1.ts +#EXTINF:10, +u-6400-m-720x408-1628-a-96-1-3-1.ts +#EXTINF:10, +u-6400-m-720x408-1628-a-96-1-4-1.ts +#EXTINF:10, +u-6400-m-720x408-1628-a-96-1-5.ts +#EXTINF:10, +u-6400-m-720x408-1628-a-96-1-6.ts +#EXTINF:10, +u-6400-m-720x408-1628-a-96-1-7.ts +#EXTINF:10, +u-6400-m-720x408-1628-a-96-1-8.ts +#EXTINF:10, +u-6400-m-720x408-1628-a-96-1-9.ts +#EXTINF:7.56, +u-6400-m-720x408-1628-a-96-1-10.ts +#EXTINF:2.44, +u-6400-m-720x408-1628-a-96-1-11.ts +#EXT-X-DISCONTINUITY +#EXTINF:10, +u-6400-m-720x408-1628-a-96-1-1-2.ts +#EXTINF:10, +u-6400-m-720x408-1628-a-96-1-2.ts +#EXTINF:10, +u-6400-m-720x408-1628-a-96-1-3-2.ts +#EXTINF:10, +u-6400-m-720x408-1628-a-96-1-4-2.ts +#EXTINF:10, +u-6400-m-720x408-1628-a-96-1-5-1.ts +#EXT-X-DISCONTINUITY +#EXT-X-KEY:METHOD=AES-128,URI="key5.json?f=1041&s=0&p=1822791&m=1506045858",IV=0x000000000000000000000000001BD047 +#EXTINF:9.72, +1041_6_1822791.ts?m=1506045858 +#EXT-X-KEY:METHOD=AES-128,URI="key6.json?f=1041&s=0&p=1822792&m=1506045858",IV=0x000000000000000000000000001BD048 +#EXTINF:10, +1041_6_1822792.ts?m=1506045858 +#EXT-X-KEY:METHOD=AES-128,URI="key7.json?f=1041&s=0&p=1822793&m=1506045858",IV=0x000000000000000000000000001BD049 +#EXTINF:10, +1041_6_1822793.ts?m=1506045858 +#EXT-X-KEY:METHOD=AES-128,URI="key8.json?f=1041&s=0&p=1822794&m=1506045858",IV=0x000000000000000000000000001BD04A +#EXTINF:10, +1041_6_1822794.ts?m=1506045858 +#EXT-X-KEY:METHOD=AES-128,URI="key9.json?f=1041&s=0&p=1822795&m=1506045858",IV=0x000000000000000000000000001BD04B +#EXTINF:10, +1041_6_1822795.ts?m=1506045858 +#EXT-X-ENDLIST \ No newline at end of file