commit a5e36fbfb60aadd58d7a06c29272251c0a8f8c0e from: Oliver Lowe date: Tue May 20 05:51:19 2025 UTC m3u8: define segment parsing once It's a bit of a hack that we read one item too many from the lexer. This change modifies segment parsing by sending the already consumed lexer items and subsequent lexer items back through another channel. Maintaining the original order through a channel means we can parse a segment in a single loop. Much shorter than dealing with the first item and all other items separately. This puts off looking into any redesigns of the lexer. For now...? commit - 093b7480bc7275b0047361f2f87d3992bc71e5ee commit + a5e36fbfb60aadd58d7a06c29272251c0a8f8c0e blob - 3789c873abda6c7313d8e49d44f5bce71970f434 blob + f20f8a3d8585da03597a22dc361b29eebb0727f2 --- m3u8/segment.go +++ m3u8/segment.go @@ -28,59 +28,43 @@ const ( // parseSegment returns the next segment from items and the leading // item which indicated the start of a segment. func parseSegment(items chan item, leading item) (*Segment, error) { + // we've already read one item, so send everything through again + // starting with the leading item to maintain the lexer's original order. + segItems := make(chan item) + go func() { + segItems <- leading + for it := range items { + if it.typ == itemURL { + segItems <- it + close(segItems) + return + } + segItems <- it + } + }() + var seg Segment - switch leading.typ { - case itemTag: - switch leading.val { - case tagSegmentDuration: - it := <-items - dur, err := parseSegmentDuration(it) - if err != nil { - return nil, fmt.Errorf("parse segment duration: %w", err) - } - seg.Duration = dur - case tagKey: - key, err := parseKey(items) - if err != nil { - 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) - } - } - - for it := range items { + for it := range segItems { switch it.typ { + case itemNewline: + continue case itemError: return nil, errors.New(it.val) case itemURL: seg.URI = it.val return &seg, nil - case itemNewline: - continue - default: - if it.typ != itemTag { - return nil, fmt.Errorf("unexpected %s", it) - } } switch it.val { case tagSegmentDuration: - it = <-items + it = <-segItems dur, err := parseSegmentDuration(it) if err != nil { return nil, fmt.Errorf("parse segment duration: %w", err) } seg.Duration = dur case tagByteRange: - it = <-items + it = <-segItems r, err := parseByteRange(it.val) if err != nil { return nil, fmt.Errorf("parse byte range: %w", err) @@ -89,19 +73,19 @@ func parseSegment(items chan item, leading item) (*Seg case tagDiscontinuity: seg.Discontinuity = true case tagKey: - key, err := parseKey(items) + key, err := parseKey(segItems) if err != nil { return nil, fmt.Errorf("parse key: %w", err) } seg.Key = &key case tagMap: - m, err := parseMap(items) + m, err := parseMap(segItems) if err != nil { return nil, fmt.Errorf("parse map: %w", err) } seg.Map = &m case tagDateTime: - it = <-items + it = <-segItems t, err := time.Parse(time.RFC3339Nano, it.val) if err != nil { return nil, fmt.Errorf("bad date time tag: %w", err)