streaming

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

time.go (4285B)


      1 package sdp
      2 
      3 import (
      4 	"errors"
      5 	"fmt"
      6 	"math"
      7 	"strconv"
      8 	"strings"
      9 	"time"
     10 )
     11 
     12 // number of seconds from the zero time used in SDP; 1900-01-01T00:00Z
     13 // to the Unix epoch.
     14 const sinceTimeZero = 2208988800
     15 
     16 func parseTimes(s string) ([2]time.Time, error) {
     17 	var times [2]time.Time
     18 	fields := strings.Fields(s)
     19 	if len(fields) != 2 {
     20 		return times, fmt.Errorf("bad number of fields %d: need 2", len(fields))
     21 	}
     22 	start, err := strconv.Atoi(fields[0])
     23 	if err != nil {
     24 		return times, fmt.Errorf("parse start time: %w", err)
     25 	}
     26 	if start != 0 {
     27 		times[0] = time.Unix(int64(start-sinceTimeZero), 0).UTC()
     28 	}
     29 	end, err := strconv.Atoi(fields[1])
     30 	if err != nil {
     31 		return times, fmt.Errorf("parse end time: %w", err)
     32 	}
     33 	if end != 0 {
     34 		times[1] = time.Unix(int64(end-sinceTimeZero), 0).UTC()
     35 	}
     36 	return times, nil
     37 }
     38 
     39 // Repeat represents a session's repetition cycle as described in
     40 // RFC 8866 section 5.10.
     41 type Repeat struct {
     42 	Interval time.Duration   // duration between each repetition cycle
     43 	Active   time.Duration   // planned duration of each session
     44 	Offsets  []time.Duration // duration(s) between each session
     45 }
     46 
     47 func (rp *Repeat) String() string {
     48 	interval := math.Round(rp.Interval.Round(time.Second).Seconds())
     49 	active := math.Round(rp.Active.Round(time.Second).Seconds())
     50 	s := fmt.Sprintf("r=%d %d ", int(interval), int(active))
     51 	if len(rp.Offsets) > 0 {
     52 		ss := make([]string, len(rp.Offsets))
     53 		for i := range rp.Offsets {
     54 			ss[i] = fmt.Sprintf("%f", rp.Offsets[i].Round(time.Second).Seconds())
     55 		}
     56 		s += strings.Join(ss, " ")
     57 	}
     58 	return strings.TrimSpace(s)
     59 }
     60 
     61 func parseRepeat(s string) (Repeat, error) {
     62 	// guard against negative durations, decimals.
     63 	// these are valid for time.ParseDuration, but not for our Repeat.
     64 	if strings.Contains(s, "-") || strings.Contains(s, ".") {
     65 		return Repeat{}, errors.New("invalid duration")
     66 	}
     67 
     68 	fields := strings.Fields(s)
     69 	if len(fields) < 3 {
     70 		return Repeat{}, fmt.Errorf("short line: have %d, want at least %d fields", len(fields), 3)
     71 	}
     72 
     73 	var repeat Repeat
     74 	var err error
     75 	repeat.Interval, err = parseDuration(fields[0])
     76 	if err != nil {
     77 		return Repeat{}, fmt.Errorf("parse interval %s: %w", fields[0], err)
     78 	}
     79 	repeat.Active, err = parseDuration(fields[1])
     80 	if err != nil {
     81 		return Repeat{}, fmt.Errorf("parse active duration %s: %w", fields[1], err)
     82 	}
     83 	for _, s := range fields[2:] {
     84 		offset, err := parseDuration(s)
     85 		if err != nil {
     86 			return Repeat{}, fmt.Errorf("parse offset %s: %w", s, err)
     87 		}
     88 		repeat.Offsets = append(repeat.Offsets, offset)
     89 	}
     90 	return repeat, nil
     91 }
     92 
     93 // parseDuration wraps time.ParseDuration to account for
     94 // bare integers (seconds) and
     95 // the day prefix ("d").
     96 func parseDuration(s string) (time.Duration, error) {
     97 	// a bare int, like 86400
     98 	i, err := strconv.Atoi(s)
     99 	if err == nil {
    100 		return time.Duration(i) * time.Second, nil
    101 	}
    102 
    103 	// a duration string like 24h
    104 	dur, err := time.ParseDuration(s)
    105 	if err == nil {
    106 		return dur, nil
    107 	}
    108 
    109 	// a duration string with days suffix, like 1d
    110 	// [0-9]+d
    111 	if !strings.HasSuffix(s, "d") {
    112 		return 0, fmt.Errorf("bad duration: expected d suffix for days")
    113 	}
    114 	j, err := strconv.Atoi(s[:len(s)-1])
    115 	if err != nil {
    116 		return 0, fmt.Errorf("parse days: %w", err)
    117 	}
    118 	return time.Duration(j) * 24 * time.Hour, nil
    119 }
    120 
    121 type TimeAdjustment struct {
    122 	When   time.Time
    123 	Offset time.Duration
    124 }
    125 
    126 func (t TimeAdjustment) String() string {
    127 	return fmt.Sprintf("%d %f", t.When.Unix()+sinceTimeZero, t.Offset.Round(time.Second).Seconds())
    128 }
    129 
    130 func parseAdjustments(line string) ([]TimeAdjustment, error) {
    131 	fields := strings.Fields(line)
    132 	if len(fields)%2 != 0 {
    133 		return nil, fmt.Errorf("odd field count %d", len(fields))
    134 	}
    135 	var adjustments []TimeAdjustment
    136 	for i := 0; i < len(fields); i += 2 {
    137 		var adj TimeAdjustment
    138 		t, err := strconv.Atoi(fields[i])
    139 		if err != nil {
    140 			return nil, fmt.Errorf("time %s: %w", fields[i], err)
    141 		}
    142 		adj.When = time.Unix(int64(t-sinceTimeZero), 0).UTC()
    143 		adj.Offset, err = parseDuration(fields[i+1])
    144 		if err != nil {
    145 			return nil, fmt.Errorf("offset %s: %w", fields[i+1], err)
    146 		}
    147 		adjustments = append(adjustments, adj)
    148 	}
    149 	return adjustments, nil
    150 }
    151 
    152 // Now returns the current SDP timestamp; the number of seconds
    153 // since 1900-01-01T00:00Z.
    154 func Now() int {
    155 	return int(time.Now().Unix() + sinceTimeZero)
    156 }