Skip to content

Commit

Permalink
Merge pull request #123 from Dash-Industry-Forum/check-ll-timeline-av…
Browse files Browse the repository at this point in the history
…ailability

Check low-latency timeline availability
  • Loading branch information
tobbee authored Oct 17, 2023
2 parents dd64eba + f86e398 commit 53093cb
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 21 deletions.
3 changes: 1 addition & 2 deletions cmd/livesim2/app/handler_livesim.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion cmd/livesim2/app/handler_livesim_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
})
}
Expand Down
32 changes: 23 additions & 9 deletions cmd/livesim2/app/livempd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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,
},
Expand Down Expand Up @@ -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 {
Expand All @@ -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)
}
}
})
}
Expand Down
6 changes: 0 additions & 6 deletions cmd/livesim2/app/livesegment.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
138 changes: 137 additions & 1 deletion cmd/livesim2/app/livesegment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}
Expand Down Expand Up @@ -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")
}
}
3 changes: 2 additions & 1 deletion cmd/livesim2/app/templates/welcome.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ <h1>Welcome to livesim2!</h1>
<a href="https://github.com/Dash-Industry-Forum/livesim2/wiki/URL-Parameters" target="_blank">Wiki parameters page</a>.
The easiest way to configure the URLs to with such parameters is the <a href="{{.Host}}/urlgen">URL generator</a> 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
<a href="https://reference.dashif.org/dash.js/latest/samples/dash-if-reference-player/index.html">dash.js </a> or
any other configured player. However, some restrictions may apply as discussed on the Wiki page
<a href="https://github.com/Dash-Industry-Forum/livesim2/wiki/Direct-Playback">Direct Playback</a>.</p>

Expand Down
1 change: 0 additions & 1 deletion cmd/livesim2/app/timesubs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,5 @@ func genWvttCueText(fss []mp4.FullSample) (string, error) {
}
_ = box.Info(&b, "", "", " ")
}
fmt.Println(b.String())
return b.String(), nil
}

0 comments on commit 53093cb

Please sign in to comment.