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  }