streaming

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

segment.go (7667B)


      1 package m3u8
      2 
      3 import (
      4 	"bytes"
      5 	"encoding/hex"
      6 	"errors"
      7 	"fmt"
      8 	"io"
      9 	"strconv"
     10 	"strings"
     11 	"time"
     12 )
     13 
     14 // Media segment tags specified in RFC 8216 section 4.4.4.
     15 const (
     16 	tagSegmentDuration = "#EXTINF"
     17 	tagByteRange       = "#EXT-X-BYTERANGE"
     18 	tagDiscontinuity   = "#EXT-X-DISCONTINUITY"
     19 	tagKey             = "#EXT-X-KEY"
     20 	tagMap             = "#EXT-X-MAP"
     21 	tagDateTime        = "#EXT-X-PROGRAM-DATE-TIME"
     22 	tagGap             = "#EXT-X-GAP"
     23 	tagBitrate         = "#EXT-X-BITRATE"
     24 	tagPart            = "#EXT-X-PART"
     25 	tagDateRange       = "#EXT-X-DATERANGE"
     26 )
     27 
     28 // parseSegment returns the next segment from items and the leading
     29 // item which indicated the start of a segment.
     30 func parseSegment(items chan item, leading item) (*Segment, error) {
     31 	// we've already read one item, so send everything through again
     32 	// starting with the leading item to maintain the lexer's original order.
     33 	segItems := make(chan item)
     34 	go func() {
     35 		segItems <- leading
     36 		for it := range items {
     37 			if it.typ == itemURL {
     38 				segItems <- it
     39 				close(segItems)
     40 				return
     41 			}
     42 			segItems <- it
     43 		}
     44 	}()
     45 
     46 	var seg Segment
     47 	for it := range segItems {
     48 		switch it.typ {
     49 		case itemNewline:
     50 			continue
     51 		case itemError:
     52 			return nil, errors.New(it.val)
     53 		case itemURL:
     54 			seg.URI = it.val
     55 			return &seg, nil
     56 		}
     57 
     58 		switch it.val {
     59 		case tagSegmentDuration:
     60 			it = <-segItems
     61 			if it.typ != itemAttrName && it.typ != itemNumber {
     62 				return nil, fmt.Errorf("parse segment duration: unexpected %s: want attribute name or number", it)
     63 			}
     64 			dur, err := parseSegmentDuration(it.val)
     65 			if err != nil {
     66 				return nil, fmt.Errorf("parse segment duration: %w", err)
     67 			}
     68 			seg.Duration = dur
     69 
     70 			// check for the optional segment title
     71 			it = <-segItems
     72 			if it.typ == itemNewline {
     73 				continue
     74 			} else if it.typ != itemComma {
     75 				return nil, fmt.Errorf("expected comma after segment duration, got %s", it)
     76 			}
     77 			it = <-segItems
     78 			seg.Title = it.val
     79 
     80 
     81 		case tagByteRange:
     82 			it = <-segItems
     83 			r, err := parseByteRange(it.val)
     84 			if err != nil {
     85 				return nil, fmt.Errorf("parse byte range: %w", err)
     86 			}
     87 			seg.Range = r
     88 		case tagDiscontinuity:
     89 			seg.Discontinuity = true
     90 		case tagKey:
     91 			key, err := parseKey(segItems)
     92 			if err != nil {
     93 				return nil, fmt.Errorf("parse key: %w", err)
     94 			}
     95 			seg.Key = &key
     96 		case tagMap:
     97 			m, err := parseMap(segItems)
     98 			if err != nil {
     99 				return nil, fmt.Errorf("parse map: %w", err)
    100 			}
    101 			seg.Map = &m
    102 		case tagDateTime:
    103 			it = <-segItems
    104 			t, err := time.Parse(rfc3339Milli, it.val)
    105 			if err != nil {
    106 				return nil, fmt.Errorf("bad date time tag: %w", err)
    107 			}
    108 			seg.DateTime = t
    109 		default:
    110 			return nil, fmt.Errorf("parsing %s unsupported", it)
    111 		}
    112 	}
    113 	return nil, fmt.Errorf("no url")
    114 }
    115 
    116 func parseSegmentDuration(s string) (time.Duration, error) {
    117 	// Some numbers can be converted straight to ints, e.g.:
    118 	// 	10
    119 	// 	10.000
    120 	// Others need to be converted from floating point, e.g:
    121 	// 	9.967
    122 	// Try the easiest paths first.
    123 	if !strings.Contains(s, ".") {
    124 		i, err := strconv.Atoi(s)
    125 		if err != nil {
    126 			return 0, err
    127 		}
    128 		return time.Duration(i) * time.Second, nil
    129 	}
    130 
    131 	// 10.000
    132 	before, after, _ := strings.Cut(s, ".")
    133 	var allZeroes = true
    134 	for r := range after {
    135 		if r != '0' {
    136 			allZeroes = false
    137 		}
    138 	}
    139 	if allZeroes {
    140 		i, err := strconv.Atoi(before)
    141 		if err != nil {
    142 			return 0, err
    143 		}
    144 		return time.Duration(i) * time.Second, nil
    145 	}
    146 	seconds, err := strconv.ParseFloat(s, 32)
    147 	if err != nil {
    148 		return 0, err
    149 	}
    150 	// precision based on a 90KHz clock.
    151 	microseconds := seconds * 1e6
    152 	return time.Duration(microseconds) * time.Microsecond, nil
    153 }
    154 
    155 func parseKey(items chan item) (Key, error) {
    156 	var key Key
    157 	for it := range items {
    158 		switch it.typ {
    159 		case itemError:
    160 			return key, errors.New(it.val)
    161 		case itemNewline:
    162 			return key, nil
    163 		case itemComma:
    164 			continue
    165 		default:
    166 			if it.typ != itemAttrName {
    167 				return Key{}, fmt.Errorf("expected attribute name, got %s", it.val)
    168 			}
    169 		}
    170 		v := <-items
    171 		if v.typ != itemEquals {
    172 			return key, fmt.Errorf("expected %q after %s, got %s", "=", it.typ, v)
    173 		}
    174 
    175 		switch it.val {
    176 		case "METHOD":
    177 			v = <-items
    178 			key.Method = parseEncryptMethod(v.val)
    179 			if key.Method == encryptMethodInvalid {
    180 				return key, fmt.Errorf("bad encrypt method %q", v.val)
    181 			}
    182 		case "URI":
    183 			v = <-items
    184 			key.URI = strings.Trim(v.val, `"`)
    185 		case "IV":
    186 			v = <-items
    187 			b, err := hex.DecodeString(strings.TrimPrefix(v.val, "0x"))
    188 			if err != nil {
    189 				return key, fmt.Errorf("parse initialisation vector: %w", err)
    190 			}
    191 			if len(b) != len(key.IV) {
    192 				return key, fmt.Errorf("bad initialisation length %d, want %d", len(b), len(key.IV))
    193 			}
    194 			copy(key.IV[:], b)
    195 		case "KEYFORMAT":
    196 			v = <-items
    197 			key.Format = strings.Trim(v.val, `"`)
    198 		case "KEYFORMATVERSIONS":
    199 			v = <-items
    200 			ss := strings.Split(v.val, "/")
    201 			key.FormatVersions = make([]uint32, len(ss))
    202 			for i := range ss {
    203 				n, err := strconv.Atoi(ss[i])
    204 				if err != nil {
    205 					return key, fmt.Errorf("parse key format version: %w", err)
    206 				}
    207 				key.FormatVersions[i] = uint32(n)
    208 			}
    209 		default:
    210 			return key, fmt.Errorf("unexpected attribute %q", it.val)
    211 		}
    212 	}
    213 	return key, fmt.Errorf("unexpected end of tag")
    214 }
    215 
    216 func parseMap(items chan item) (Map, error) {
    217 	var mmap Map
    218 	for it := range items {
    219 		switch it.typ {
    220 		case itemError:
    221 			return mmap, errors.New(it.val)
    222 		case itemNewline:
    223 			return mmap, nil
    224 		}
    225 		if it.typ != itemAttrName {
    226 			return Map{}, fmt.Errorf("unexpected %s %q", it.typ, it.val)
    227 		}
    228 		attr := it.val
    229 		it = <-items
    230 		if it.typ != itemEquals {
    231 			return Map{}, fmt.Errorf("expected %q after %s, got %q", "=", attr, it.val)
    232 		}
    233 
    234 		it = <-items
    235 		switch attr {
    236 		case "URI":
    237 			mmap.URI = strings.Trim(it.val, `"`)
    238 		case "BYTERANGE":
    239 			r, err := parseByteRange(it.val)
    240 			if err != nil {
    241 				return Map{}, fmt.Errorf("parse byte range: %w", err)
    242 			}
    243 			mmap.ByteRange = r
    244 		default:
    245 			return Map{}, fmt.Errorf("unexpected attribute %q", it.val)
    246 		}
    247 	}
    248 	return Map{}, fmt.Errorf("unexpected end of tag")
    249 }
    250 
    251 func writeSegments(w io.Writer, segments []Segment) (n int, err error) {
    252 	for i, seg := range segments {
    253 		b, err := seg.MarshalText()
    254 		if err != nil {
    255 			return n, fmt.Errorf("segment %d: %w", i, err)
    256 		}
    257 		nn, err := fmt.Fprintln(w, string(b))
    258 		n += nn
    259 		if err != nil {
    260 			return n, err
    261 		}
    262 	}
    263 	return n, nil
    264 }
    265 
    266 func (seg *Segment) MarshalText() ([]byte, error) {
    267 	if seg.URI == "" {
    268 		return nil, fmt.Errorf("empty URI")
    269 	}
    270 	if seg.Duration == 0 {
    271 		return nil, fmt.Errorf("zero duration")
    272 	}
    273 	var tags []string
    274 	if seg.Discontinuity {
    275 		tags = append(tags, tagDiscontinuity)
    276 	}
    277 	if seg.DateRange != nil {
    278 		buf := &bytes.Buffer{}
    279 		if err := writeDateRange(buf, seg.DateRange); err != nil {
    280 			return nil, fmt.Errorf("write date range: %w", err)
    281 		}
    282 		tags = append(tags, buf.String())
    283 	}
    284 	if seg.Range != [2]int{0, 0} {
    285 		if seg.Range[0] >= seg.Range[1] {
    286 			return nil, fmt.Errorf("impossible range: offset (%d) must be smaller than next %d", seg.Range[0], seg.Range[1])
    287 		}
    288 		tags = append(tags, fmt.Sprintf("%s:%s", tagByteRange, seg.Range))
    289 	}
    290 	if seg.Key != nil {
    291 		tags = append(tags, seg.Key.String())
    292 	}
    293 	if seg.Map != nil {
    294 		tags = append(tags, seg.Map.String())
    295 	}
    296 	if !seg.DateTime.IsZero() {
    297 		tags = append(tags, fmt.Sprintf("%s:%s", tagDateTime, seg.DateTime.Format(rfc3339Milli)))
    298 	}
    299 	us := seg.Duration / time.Microsecond
    300 	// we do .03f for the same precision as test-streams.mux.dev.
    301 	durTag := fmt.Sprintf("%s:%.03f", tagSegmentDuration, float32(us)/1e6)
    302 	if seg.Title != "" {
    303 		durTag += ","+seg.Title
    304 	}
    305 	tags = append(tags, durTag)
    306 	tags = append(tags, seg.URI)
    307 	return []byte(strings.Join(tags, "\n")), nil
    308 }