commit - 9d0a9c380ce962f56c141d6db6fdacd7a41bf831
commit + 3e2200b9e76d35b13e385535acdd87583441afbe
blob - 72a79eb6300b633779d9890cb85f70b5f0cb7bc8
blob + 0e22fc020003e7247af625a953ad3f112aa4c8f8
--- m3u8/lex.go
+++ m3u8/lex.go
"io"
"os"
"strings"
+ "unicode"
"unicode/utf8"
)
return lexAttrValue(l)
case r == '@':
return lexAttrValue(l)
+ case r == ':':
+ return lexAttrValue(l)
case r == '"':
l.next()
return lexQString(l)
func lexAttrValue(l *lexer) stateFn {
r := l.next()
switch r {
- case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.':
+ case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', ':':
return lexNumber(l)
case '"':
return lexQString(l)
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.':
l.next()
continue
+ case ':', 'T', 'Z':
+ // could be a RFC 3339 timestamp
+ l.next()
+ if !unicode.IsDigit(l.peek()) {
+ return l.errorf("expected digit after timestamp character %c, got %c", r, l.peek())
+ }
+ return lexRawString(l)
default:
l.emit(itemNumber)
return lexAttrs(l)
blob - cad16cdf5b3e2c9aeaa5df7f69207b3151bc066a
blob + fb10f2118acd9cb9ba8899c4e2834c6b92a94123
--- m3u8/m3u8.go
+++ m3u8/m3u8.go
type Segment struct {
URI string
- // Duration of this specific segment from the #EXTINF tag.
+
+ // Duration of this specific segment from the EXTINF tag.
Duration time.Duration
+
// Indicates this segment holds a subset of the segment point to by URI.
- // Range is the length of the subsegment from from the #EXT-X-BYTERANGE tag.
+ // Range is the length of the subsegment from the EXT-X-BYTERANGE tag.
Range ByteRange
+
// If true, the preceding segment and the following segment
// are discontinuous. For example, this segment is part of a
// commercial break.
Discontinuity bool
+
// Holds information on how to decrypt this segment.
// If nil, the segment is not encrypted.
- Key *Key
- Map *Map
- DateTime time.Time
+ Key *Key
+
+ Map *Map
+
+ // Associates an absolute time with the start of the segment.
+ // The EXT-X-PROGRAM-DATE-TIME tag holds this value to
+ // millisecond accuracy.
+ DateTime time.Time
+
DateRange *DateRange
}
blob - 0d706d6f50a48b0bc89983ee9fa72a8a9c18b968
blob + 3789c873abda6c7313d8e49d44f5bce71970f434
--- m3u8/segment.go
+++ m3u8/segment.go
return nil, fmt.Errorf("parse key: %w", err)
}
seg.Key = &key
+ case tagMap:
+ m, err := parseMap(items)
+ if err != nil {
+ return nil, fmt.Errorf("parse map: %w", err)
+ }
+ seg.Map = &m
default:
return nil, fmt.Errorf("parse leading item %s: unsupported", leading)
}
return nil, fmt.Errorf("parse map: %w", err)
}
seg.Map = &m
+ case tagDateTime:
+ it = <-items
+ t, err := time.Parse(time.RFC3339Nano, it.val)
+ if err != nil {
+ return nil, fmt.Errorf("bad date time tag: %w", err)
+ }
+ seg.DateTime = t
default:
return nil, fmt.Errorf("parsing %s unsupported", it)
}
return mmap, errors.New(it.val)
case itemNewline:
return mmap, nil
- case itemAttrName:
- v := <-items
- if v.typ != itemEquals {
- return Map{}, fmt.Errorf("expected %q after %s, got %s", "=", it.typ, v)
+ }
+ if it.typ != itemAttrName {
+ return Map{}, fmt.Errorf("unexpected %s %q", it.typ, it.val)
+ }
+ attr := it.val
+ it = <-items
+ if it.typ != itemEquals {
+ return Map{}, fmt.Errorf("expected %q after %s, got %q", "=", attr, it.val)
+ }
+
+ it = <-items
+ switch attr {
+ case "URI":
+ mmap.URI = strings.Trim(it.val, `"`)
+ case "BYTERANGE":
+ r, err := parseByteRange(it.val)
+ if err != nil {
+ return Map{}, fmt.Errorf("parse byte range: %w", err)
}
- switch it.val {
- case "URI":
- v = <-items
- mmap.URI = strings.Trim(it.val, `"`)
- case "BYTERANGE":
- v = <-items
- r, err := parseByteRange(v.val)
- if err != nil {
- return Map{}, fmt.Errorf("parse byte range: %w", err)
- }
- mmap.ByteRange = r
- default:
- return Map{}, fmt.Errorf("unexpected attribute %q", it.val)
- }
+ mmap.ByteRange = r
+ default:
+ return Map{}, fmt.Errorf("unexpected attribute %q", it.val)
}
}
return Map{}, fmt.Errorf("unexpected end of tag")
blob - c9c777d8c62b46c04c954a12fc84294e87fd250b
blob + 06f9851ab3e4f2273de6dc4d044dc7b4c2a95a2b
--- m3u8/segment_test.go
+++ m3u8/segment_test.go
URI: "key1.json?f=1041&s=0&p=1822767&m=1506045858",
IV: [...]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x1B, 0xD0, 0x2F},
},
- URI: "1041_6_1822767.ts?m=1506045858",
+ DateTime: time.Date(2020, 12, 2, 18, 33, 3, 447e6, time.UTC),
+ URI: "1041_6_1822767.ts?m=1506045858",
}
if !reflect.DeepEqual(plist.Segments[0], encrypted) {
blob - 79e1a52b3948e69c1df6200c4fcc9cb9165588a3
blob + 8050818b9274cc31ecf069dce7f58d61b7a07272
--- m3u8/testdata/discontinuities.m3u8
+++ m3u8/testdata/discontinuities.m3u8
#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
+#EXT-X-PROGRAM-DATE-TIME:2020-12-02T18:33:03.447Z
#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