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") + } +}