Commit Diff


commit - b80fe72bf0cda6e47989801e6e86bb43a03bb2d7
commit + 34d647b964d037705c244de67dc552c4ce4615ca
blob - bcc642c48bfe1dd82e61480b9e2d5039f6f4cfcf
blob + 4322f741a1d5e112fd23905798979f462583b578
--- cmcd/cmcd.go
+++ cmcd/cmcd.go
@@ -177,10 +177,8 @@ type Session struct {
 	// A GUID uniquely identifying the session, no longer than 64
 	// characters.
 	ID string
-	// Type of the stream. If false, all playback segments are
-	// available. Otherwise, the stream is considered live, and
-	// segments become available over time.
-	Live bool
+	// Type of the stream.
+	StreamType
 	// A unique identifier of the client's requested content, no
 	// longer than 64 characters.
 	ContentID string
@@ -191,7 +189,7 @@ type Session struct {
 	version int
 }
 
-type PlayRate uint8
+type PlayRate float32
 
 const (
 	Stopped PlayRate = iota
@@ -199,6 +197,15 @@ const (
 	DoubleTime
 )
 
+type StreamType string
+
+const (
+	StreamTypeLive = "l"
+	StreamTypeVOD  = "v"
+)
+
+func (st StreamType) Live() bool { return st == StreamTypeLive }
+
 type StreamFormat byte
 
 const (
blob - ff0ea49c4660baa3cae04b5e4bea918c48b357cf
blob + 43ce46bdbc9d6a0dffdb502cca309dde37f15a07
--- cmcd/encode.go
+++ cmcd/encode.go
@@ -74,7 +74,7 @@ func (s Session) Encode() string {
 	}
 	// "SHOULD only be sent if not equal to 1": CTA-5004 page 10.
 	if s.PlayRate != RealTime {
-		attrs = append(attrs, fmt.Sprintf("pr=%d", s.PlayRate))
+		attrs = append(attrs, fmt.Sprintf("pr=%v", s.PlayRate))
 	}
 	switch s.Format {
 	case FormatDASH, FormatHLS, FormatSmooth, FormatOther:
blob - 9953bedc24fc87e74c1617770ec879ca933a5575
blob + 17b47b0646e4807d119c73cea84547a759baebe1
--- cmcd/parse.go
+++ cmcd/parse.go
@@ -132,21 +132,16 @@ func parseSession(attrs map[string]string) (Session, e
 			ses.ID = strings.Trim(v, `"`)
 		case "st":
 			// TODO(otl): what if it's not "l"?
-			if v == "l" {
-				ses.Live = true
-			}
+			ses.StreamType = StreamType(v)
 		case "cid":
 			ses.ContentID = strings.Trim(v, `"`)
 		case "pr":
-			i, err := strconv.Atoi(v)
+			i, err := strconv.ParseFloat(v, 32)
 			if err != nil {
 				return ses, fmt.Errorf("play rate: %w", err)
 			}
 			// TODO(otl): what is this max value? why?
 			// Include in the error message.
-			if i > 2 {
-				return ses, fmt.Errorf("play rate: %d greater than max %d", i, 2)
-			}
 			ses.PlayRate = PlayRate(i)
 		case "sf":
 			if len(v) != 1 {
blob - c2237430c715ac2745d64bf9e4e87a57db4bc573
blob + 5bf1b26ece0d4e63f32e54c2948ad3bde85f309d
--- cmcd/parse_test.go
+++ cmcd/parse_test.go
@@ -15,10 +15,10 @@ import (
 )
 
 var tests = map[string]Info{
-	"testdata/simple": Info{
+	"testdata/simple": {
 		Session: Session{ID: "6e2fb550-c457-11e9-bb97-0800200c9a66", PlayRate: RealTime},
 	},
-	"testdata/all_four": Info{
+	"testdata/all_four": {
 		Request: Request{Throughput: 25400},
 		Object: Object{
 			Bitrate:    3200,
@@ -29,17 +29,17 @@ var tests = map[string]Info{
 		Status:  Status{true, 15000},
 		Session: Session{ID: "6e2fb550-c457-11e9-bb97-0800200c9a66", PlayRate: RealTime},
 	},
-	"testdata/booleans": Info{
+	"testdata/booleans": {
 		Status:  Status{true, 0},
 		Request: Request{Startup: true},
 		Session: Session{PlayRate: RealTime},
 	},
-	"testdata/range": Info{
+	"testdata/range": {
 		Request: Request{NextRange: [2]int{12323, 48763}},
 		Object:  Object{Duration: 4004 * time.Millisecond},
 		Session: Session{PlayRate: RealTime},
 	},
-	"testdata/custom": Info{
+	"testdata/custom": {
 		Object:  Object{Duration: 4004 * time.Millisecond},
 		Session: Session{PlayRate: RealTime},
 		Custom: map[string]any{