github.com/puellanivis/breton@v0.2.16/lib/net/dash/stream.go (about) 1 package dash 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "io" 8 "strings" 9 "time" 10 11 "github.com/puellanivis/breton/lib/files" 12 "github.com/puellanivis/breton/lib/glog" 13 "github.com/puellanivis/breton/lib/net/dash/mpd" 14 15 "github.com/pkg/errors" 16 ) 17 18 // Stream is a structure holding the information necessary to retreive a# 19 // DASH stream. 20 type Stream struct { 21 w io.Writer 22 m *Manifest 23 24 metrics *metricsPack 25 26 // so we can find the appropriate SegmentTimeline 27 // in a given mpd.MPD 28 pid string 29 aid uint 30 31 eof bool 32 dynamic bool 33 34 base string 35 init, media string 36 bw uint 37 repID string 38 time uint64 39 } 40 41 // Bandwidth returns the bandwidth that was selected for this Stream. 42 func (s *Stream) Bandwidth() uint { 43 return s.bw 44 } 45 46 // RepresentationID returns the ID of the Representationn that was selected for this Stream. 47 func (s *Stream) RepresentationID() string { 48 return s.repID 49 } 50 51 // buildURL takes the given template, and given number, and renders it 52 // according to the DASH standards. 53 func (s *Stream) buildURL(template string, number uint) string { 54 b := new(bytes.Buffer) 55 56 tmpl := []byte(template) 57 58 for { 59 i := bytes.IndexByte(tmpl, '$') 60 if i < 0 { 61 break 62 } 63 64 b.Write(tmpl[:i]) 65 tmpl = tmpl[i+1:] 66 67 j := bytes.IndexByte(tmpl, '$') 68 if j < 0 { 69 break 70 } 71 72 var v string 73 v, tmpl = string(tmpl[:j]), tmpl[j+1:] 74 75 format, val := "%v", v 76 77 idx := strings.IndexByte(v, '%') 78 if idx > 0 { 79 format, val = v[idx:], v[:idx] 80 } 81 82 switch val { 83 case "": 84 fmt.Fprint(b, "$") 85 86 case "Time": 87 fmt.Fprintf(b, format, s.time) 88 case "Bandwidth": 89 fmt.Fprintf(b, format, s.bw) 90 case "RepresentationID": 91 fmt.Fprintf(b, format, s.repID) 92 case "Number": 93 fmt.Fprintf(b, format, number) 94 } 95 } 96 97 b.Write(tmpl) 98 99 return b.String() 100 } 101 102 // Init reads the initialization URL from the stream. 103 func (s *Stream) Init(ctx context.Context) error { 104 ctx, err := files.WithRoot(ctx, s.base) 105 if err != nil { 106 return err 107 } 108 109 init := s.buildURL(s.init, 0) 110 return s.readFrom(ctx, init, 0) 111 } 112 113 // readTo reads a given URL into the Stream’s io.Writer while keeping metrics. 114 func (s *Stream) readFrom(ctx context.Context, url string, scale float64) error { 115 done := s.metrics.timing.Timer() 116 defer done() 117 118 ctx, err := files.WithRoot(ctx, s.base) 119 if err != nil { 120 return err 121 } 122 123 if glog.V(5) { 124 root, _ := files.GetRoot(ctx) 125 126 glog.Infof("Grabbing: %s %s", root, url) 127 } 128 129 f, err := files.Open(ctx, url) 130 if err != nil { 131 return err 132 } 133 defer f.Close() 134 135 n, err := io.Copy(s.w, f) 136 if scale > 0.1 { 137 // n is measured in bytes, we want to record in bits per time unit 138 s.metrics.bandwidth.Observe(float64(n*8) * scale) 139 } 140 141 return err 142 } 143 144 func (s *Stream) readTimeline(ctx context.Context, ts uint, num uint, tl *mpd.SegmentTimeline) (time.Duration, error) { 145 if s.eof { 146 return 0, io.EOF 147 } 148 149 var dur, tdur time.Duration 150 var tscale = time.Second 151 if ts != 0 { 152 tscale = time.Second / time.Duration(ts) 153 } 154 155 for _, seg := range tl.S { 156 t := seg.T 157 158 if s.time <= t { 159 s.time = t 160 } 161 162 dur = time.Duration(seg.D) * tscale 163 scale := 1 / dur.Seconds() 164 165 // less than or equal to, because it includes an implicit first 166 for i := 0; i <= seg.R; i++ { 167 if t <= s.time { 168 t += seg.D 169 continue 170 } 171 172 s.time = t 173 174 url := s.buildURL(s.media, num) 175 176 if err := s.readFrom(ctx, url, scale); err != nil { 177 return tdur, err 178 } 179 180 num++ 181 tdur += dur 182 t += seg.D 183 } 184 } 185 186 if !s.dynamic { 187 s.eof = true 188 return tdur, io.EOF 189 } 190 191 return tdur, nil 192 } 193 194 // Read reads the next series of segments in the Stream. 195 // If the returned time.Duration is 0, it means that no new segments were available. 196 // (In which case, you’re likely calling this function too often in this case. 197 // Calling any faster than the duration returned by MinimumUpdatePeriod is just a waste of cycles.) 198 func (s *Stream) Read(ctx context.Context) (time.Duration, error) { 199 cur, err := s.m.m.get(ctx) 200 if err != nil { 201 return 0, err 202 } 203 204 for _, p := range cur.Period { 205 if p.Id != s.pid { 206 continue 207 } 208 209 for _, as := range p.AdaptationSet { 210 if as.Id != s.aid { 211 continue 212 } 213 214 tmpl := as.SegmentTemplate 215 if tmpl == nil { 216 return 0, errors.New("SegmentTemplate not found") 217 } 218 219 if tmpl.SegmentTimeline == nil { 220 return 0, errors.New("SegmentTimeline not found") 221 } 222 223 return s.readTimeline(ctx, tmpl.Timescale, tmpl.StartNumber, tmpl.SegmentTimeline) 224 } 225 } 226 227 return 0, fmt.Errorf("no media could be found for pid: %s, aid: %d", s.pid, s.aid) 228 }