From a863a19b7f37394c5baee05fd3131416f3c94f16 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Torbj=C3=B6rn=20Einarson?=
Date: Sat, 14 Oct 2023 08:56:20 +0200
Subject: [PATCH 1/3] chore: remove/replace Print in tests
---
cmd/livesim2/app/handler_livesim_test.go | 2 +-
cmd/livesim2/app/livesegment_test.go | 2 +-
cmd/livesim2/app/templates/welcome.html | 3 ++-
cmd/livesim2/app/timesubs_test.go | 1 -
4 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/cmd/livesim2/app/handler_livesim_test.go b/cmd/livesim2/app/handler_livesim_test.go
index ff68d43..d88b537 100644
--- a/cmd/livesim2/app/handler_livesim_test.go
+++ b/cmd/livesim2/app/handler_livesim_test.go
@@ -73,7 +73,7 @@ func TestParamToMPD(t *testing.T) {
return
}
bodyStr := string(body)
- //fmt.Println(bodyStr)
+ t.Logf("length of body %d", len(bodyStr))
require.Greater(t, strings.Index(bodyStr, tc.wantedInMPD), -1, "no match in MPD")
})
}
diff --git a/cmd/livesim2/app/livesegment_test.go b/cmd/livesim2/app/livesegment_test.go
index 10f49a4..8c74e72 100644
--- a/cmd/livesim2/app/livesegment_test.go
+++ b/cmd/livesim2/app/livesegment_test.go
@@ -155,7 +155,7 @@ func TestCheckAudioSegmentTimeAddressing(t *testing.T) {
trun := so.seg.Fragments[0].Moof.Traf.Trun
nrSamples := c.nrSamplesMod[nr%len(c.nrSamplesMod)]
require.Equal(t, nrSamples, len(trun.Samples))
- fmt.Printf("nr %d segData: %s mpdType: %s mediaTime: %d\n", nr, so.meta.rep.SegmentType(), mpdType, mediaTime) // TODO. Remove
+ t.Logf("nr %d segData: %s mpdType: %s mediaTime: %d\n", nr, so.meta.rep.SegmentType(), mpdType, mediaTime)
}
}
}
diff --git a/cmd/livesim2/app/templates/welcome.html b/cmd/livesim2/app/templates/welcome.html
index 98991d8..77bcfe6 100644
--- a/cmd/livesim2/app/templates/welcome.html
+++ b/cmd/livesim2/app/templates/welcome.html
@@ -26,7 +26,8 @@ Welcome to livesim2!
Wiki parameters page.
The easiest way to configure the URLs to with such parameters is the URL generator page.
- Note, the URL generator and other pages include links for direct playback using online versions of `dash.js` or
+ Note, the URL generator and other pages include links for direct playback using online versions of
+ dash.js or
any other configured player. However, some restrictions may apply as discussed on the Wiki page
Direct Playback.
diff --git a/cmd/livesim2/app/timesubs_test.go b/cmd/livesim2/app/timesubs_test.go
index b9491ea..8247f63 100644
--- a/cmd/livesim2/app/timesubs_test.go
+++ b/cmd/livesim2/app/timesubs_test.go
@@ -441,6 +441,5 @@ func genWvttCueText(fss []mp4.FullSample) (string, error) {
}
_ = box.Info(&b, "", "", " ")
}
- fmt.Println(b.String())
return b.String(), nil
}
From b545d2b7389f976d79d8c048cf1c2a6f129ab4bb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Torbj=C3=B6rn=20Einarson?=
Date: Sun, 15 Oct 2023 19:24:59 +0200
Subject: [PATCH 2/3] chore: clean comments
---
cmd/livesim2/app/handler_livesim.go | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/cmd/livesim2/app/handler_livesim.go b/cmd/livesim2/app/handler_livesim.go
index 1c8a758..44fc607 100644
--- a/cmd/livesim2/app/handler_livesim.go
+++ b/cmd/livesim2/app/handler_livesim.go
@@ -83,14 +83,13 @@ func (s *Server) livesimHandlerFunc(w http.ResponseWriter, r *http.Request) {
cfg.SetHost(s.Cfg.Host, r)
err := writeLiveMPD(log, w, cfg, a, mpdName, nowMS)
if err != nil {
- // TODO. Add more granular errors like 404 not found
msg := fmt.Sprintf("liveMPD: %s", err)
log.Error(msg)
http.Error(w, msg, http.StatusInternalServerError)
return
}
case ".mp4", ".m4s", ".cmfv", ".cmfa", ".cmft", ".jpg", ".jpeg", ".m4v", ".m4a":
- segmentPart := strings.TrimPrefix(contentPart, a.AssetPath) // includes heading /
+ segmentPart := strings.TrimPrefix(contentPart, a.AssetPath) // includes heading slash
err = writeSegment(r.Context(), w, log, cfg, s.assetMgr.vodFS, a, segmentPart[1:], nowMS, s.textTemplates)
if err != nil {
var tooEarly errTooEarly
From f86e39892a1f8b6c0268a62cead79404dd1da0c5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Torbj=C3=B6rn=20Einarson?=
Date: Tue, 17 Oct 2023 14:00:05 +0200
Subject: [PATCH 3/3] test: add more tests for when segments are available for
low latency
---
cmd/livesim2/app/livempd_test.go | 32 +++++--
cmd/livesim2/app/livesegment.go | 6 --
cmd/livesim2/app/livesegment_test.go | 136 +++++++++++++++++++++++++++
3 files changed, 159 insertions(+), 15 deletions(-)
diff --git a/cmd/livesim2/app/livempd_test.go b/cmd/livesim2/app/livempd_test.go
index d921aa4..7feb241 100644
--- a/cmd/livesim2/app/livempd_test.go
+++ b/cmd/livesim2/app/livempd_test.go
@@ -234,6 +234,7 @@ func TestSegmentTimes(t *testing.T) {
}
}
+// TestLastAvailableSegment tests that the last available segment is correct including low latency.
func TestLastAvailableSegment(t *testing.T) {
vodFS := os.DirFS("testdata/assets")
tmpDir := t.TempDir()
@@ -258,6 +259,16 @@ func TestLastAvailableSegment(t *testing.T) {
segTimelineTime: true,
availabilityTimeOffset: 1.5,
availabilityTimeComplete: false,
+ nowMS: 3_600_501,
+ wantedSegNr: 1800,
+ },
+ {
+ desc: "Timeline with $Time$ 1hour+1s with chunkdur 0.5",
+ asset: "WAVE/vectors/cfhd_sets/12.5_25_50/t3/2022-10-17",
+ mpdName: "stream.mpd",
+ segTimelineTime: false,
+ availabilityTimeOffset: 1.5,
+ availabilityTimeComplete: false,
nowMS: 3_601_000,
wantedSegNr: 1800,
},
@@ -314,6 +325,8 @@ func TestLastAvailableSegment(t *testing.T) {
cfg := NewResponseConfig()
if tc.segTimelineTime {
cfg.SegTimelineFlag = true
+ } else {
+ cfg.SegTimelineNrFlag = true
}
cfg.AvailabilityTimeOffsetS = tc.availabilityTimeOffset
if tc.availabilityTimeOffset > 0 && !tc.availabilityTimeComplete {
@@ -324,15 +337,16 @@ func TestLastAvailableSegment(t *testing.T) {
wTimes := calcWrapTimes(asset, cfg, tc.nowMS, tsbd)
mpd, err := asset.getVodMPD(tc.mpdName)
require.NoError(t, err)
- as := mpd.Periods[0].AdaptationSets[0]
- atoMS, err := setOffsetInAdaptationSet(cfg, asset, as)
- if tc.wantedErr != "" {
- require.EqualError(t, err, tc.wantedErr)
- } else {
- require.NoError(t, err)
- r := as.Representations[0] // Assume that any representation will be fine inside AS
- se := asset.generateTimelineEntries(r.Id, wTimes, atoMS)
- assert.Equal(t, tc.wantedSegNr, se.lsi.nr)
+ for _, as := range mpd.Periods[0].AdaptationSets {
+ atoMS, err := setOffsetInAdaptationSet(cfg, asset, as)
+ if tc.wantedErr != "" {
+ require.EqualError(t, err, tc.wantedErr)
+ } else {
+ require.NoError(t, err)
+ r := as.Representations[0] // Assume that any representation will be fine inside AS
+ se := asset.generateTimelineEntries(r.Id, wTimes, atoMS)
+ assert.Equal(t, tc.wantedSegNr, se.lsi.nr)
+ }
}
})
}
diff --git a/cmd/livesim2/app/livesegment.go b/cmd/livesim2/app/livesegment.go
index 2219980..1aec090 100644
--- a/cmd/livesim2/app/livesegment.go
+++ b/cmd/livesim2/app/livesegment.go
@@ -139,9 +139,6 @@ func findSegMetaFromTime(a *asset, rep *RepData, time uint64, cfg *ResponseConfi
// Check interval validity
segAvailTimeS := float64(int(seg.EndTime)+wrapTime+mediaRef) / float64(rep.MediaTimescale)
- if !cfg.AvailabilityTimeCompleteFlag {
- segAvailTimeS -= cfg.AvailabilityTimeOffsetS
- }
nowS := float64(nowMS) * 0.001
err := CheckTimeValidity(segAvailTimeS, nowS, float64(*cfg.TimeShiftBufferDepthS), cfg.getAvailabilityTimeOffsetS())
if err != nil {
@@ -238,9 +235,6 @@ func findSegMetaFromNr(a *asset, rep *RepData, nr uint32, cfg *ResponseConfig, n
// Check interval validity
segAvailTimeS := float64(int(seg.EndTime)+wrapTime+mediaRef) / float64(rep.MediaTimescale)
- if !cfg.AvailabilityTimeCompleteFlag {
- segAvailTimeS -= cfg.AvailabilityTimeOffsetS
- }
nowS := float64(nowMS) * 0.001
err := CheckTimeValidity(segAvailTimeS, nowS, float64(*cfg.TimeShiftBufferDepthS), cfg.getAvailabilityTimeOffsetS())
if err != nil {
diff --git a/cmd/livesim2/app/livesegment_test.go b/cmd/livesim2/app/livesegment_test.go
index 8c74e72..84a1718 100644
--- a/cmd/livesim2/app/livesegment_test.go
+++ b/cmd/livesim2/app/livesegment_test.go
@@ -422,3 +422,139 @@ func TestStartNumber(t *testing.T) {
}
}
+
+func TestLLSegmentAvailability(t *testing.T) {
+ vodFS := os.DirFS("testdata/assets")
+ am := newAssetMgr(vodFS, "", false)
+ err := am.discoverAssets()
+ require.NoError(t, err)
+ err = logging.InitSlog("error", "discard")
+ require.NoError(t, err)
+
+ cases := []struct {
+ asset string
+ media string
+ nowMS int
+ startNr int
+ mpdType string
+ requestMedia int
+ expectedNr int
+ expectedDecodeTime int
+ expectedErr string
+ }{
+ {
+ asset: "testpic_2s",
+ media: "V300/$NrOrTime$.m4s",
+ nowMS: 50_000,
+ mpdType: "Number",
+ requestMedia: 25,
+ expectedErr: "too early",
+ },
+ {
+ asset: "testpic_2s",
+ media: "V300/$NrOrTime$.m4s",
+ nowMS: 50_500,
+ mpdType: "Number",
+ requestMedia: 25,
+ expectedNr: 25,
+ expectedDecodeTime: 50 * 90000,
+ expectedErr: "",
+ },
+ {
+ asset: "testpic_2s",
+ media: "V300/$NrOrTime$.m4s",
+ nowMS: 50_500,
+ mpdType: "TimelineNumber",
+ requestMedia: 25,
+ expectedNr: 25,
+ expectedDecodeTime: 50 * 90000,
+ expectedErr: "",
+ },
+ {
+ asset: "testpic_2s",
+ media: "V300/$NrOrTime$.m4s",
+ nowMS: 50_500,
+ mpdType: "TimelineTime",
+ requestMedia: 4_500_000,
+ expectedNr: 25,
+ expectedDecodeTime: 50 * 90000,
+ expectedErr: "",
+ },
+ {
+ asset: "testpic_2s",
+ media: "V300/$NrOrTime$.m4s",
+ nowMS: 50_500,
+ startNr: 1,
+ mpdType: "Number",
+ requestMedia: 26,
+ expectedNr: 26,
+ expectedDecodeTime: 50 * 90000,
+ expectedErr: "",
+ },
+ {
+ asset: "testpic_2s",
+ media: "V300/$NrOrTime$.m4s",
+ nowMS: 50_500,
+ startNr: 1,
+ mpdType: "Number",
+ requestMedia: 27,
+ expectedErr: "too early",
+ },
+ {
+ asset: "testpic_2s",
+ media: "V300/$NrOrTime$.m4s",
+ nowMS: 50_500,
+ mpdType: "TimelineNumber",
+ startNr: 1,
+ requestMedia: 26,
+ expectedNr: 26,
+ expectedDecodeTime: 50 * 90000,
+ expectedErr: "",
+ },
+ {
+ asset: "testpic_2s",
+ media: "V300/$NrOrTime$.m4s",
+ nowMS: 50_500,
+ mpdType: "TimelineTime",
+ startNr: 1,
+ requestMedia: 4_500_000,
+ expectedNr: 26,
+ expectedDecodeTime: 50 * 90000,
+ expectedErr: "",
+ },
+ }
+ for _, tc := range cases {
+ asset, ok := am.findAsset(tc.asset)
+ require.True(t, ok)
+ require.NoError(t, err)
+ cfg := NewResponseConfig()
+ cfg.AvailabilityTimeCompleteFlag = false
+ cfg.AvailabilityTimeOffsetS = 1.5
+ switch tc.mpdType {
+ case "TimelineTime":
+ cfg.SegTimelineFlag = true
+ case "TimelineNumber":
+ cfg.SegTimelineNrFlag = true
+ case "Number":
+ // Nothing
+ default:
+ require.Fail(t, "unknown mpdType")
+ }
+ if tc.startNr != 0 {
+ cfg.StartNr = Ptr(tc.startNr)
+ }
+ media := strings.Replace(tc.media, "$NrOrTime$", fmt.Sprintf("%d", (tc.requestMedia)), 1)
+ so, err := genLiveSegment(vodFS, asset, cfg, media, tc.nowMS)
+ if tc.expectedErr != "" {
+ require.Error(t, err)
+ require.Contains(t, err.Error(), tc.expectedErr)
+ continue
+ }
+ require.NoError(t, err)
+ moof := so.seg.Fragments[0].Moof
+ seqNr := moof.Mfhd.SequenceNumber
+ require.Equal(t, tc.expectedNr, int(seqNr), "response segment sequence number")
+ decodeTime := moof.Traf.Tfdt.BaseMediaDecodeTime()
+ require.Equal(t, tc.expectedDecodeTime, int(decodeTime), "response segment decode time")
+ }
+}