-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
f3a8dca
commit c82b671
Showing
3 changed files
with
297 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
package dash | ||
|
||
import ( | ||
"time" | ||
|
||
"github.com/abema/antares/core" | ||
) | ||
|
||
type PresentationDelayInspectorConfig struct { | ||
Warn time.Duration | ||
Error time.Duration | ||
} | ||
|
||
func DefaultPresentationDelayInspectorConfig() *PresentationDelayInspectorConfig { | ||
return &PresentationDelayInspectorConfig{ | ||
Warn: 2 * time.Second, | ||
Error: 0, | ||
} | ||
} | ||
|
||
func NewPresentationDelayInspector() core.DASHInspector { | ||
return NewPresentationDelayInspectorWithConfig(DefaultPresentationDelayInspectorConfig()) | ||
} | ||
|
||
func NewPresentationDelayInspectorWithConfig(config *PresentationDelayInspectorConfig) core.DASHInspector { | ||
return &PresentationDelayInspector{ | ||
config, | ||
} | ||
} | ||
|
||
type PresentationDelayInspector struct { | ||
config *PresentationDelayInspectorConfig | ||
} | ||
|
||
func (ins *PresentationDelayInspector) Inspect(manifest *core.Manifest, segments core.SegmentStore) *core.Report { | ||
if manifest.Type != nil && *manifest.Type == "static" { | ||
return &core.Report{ | ||
Name: "PresentationDelayInspector", | ||
Severity: core.Info, | ||
Message: "skip VOD manifest", | ||
} | ||
} | ||
|
||
// get wall-clock | ||
wallClock := time.Now() | ||
if manifest.UTCTiming != nil && manifest.UTCTiming.SchemeIDURI != nil && *manifest.UTCTiming.SchemeIDURI == "urn:mpeg:dash:utc:direct:2014" { | ||
tm, err := time.Parse(time.RFC3339Nano, *manifest.UTCTiming.Value) | ||
if err != nil { | ||
return &core.Report{ | ||
Name: "PresentationDelayInspector", | ||
Severity: core.Error, | ||
Message: "invalid UTCTiming@value", | ||
Values: core.Values{"error": err}, | ||
} | ||
} | ||
wallClock = tm | ||
} else if manifest.PublishTime != nil { | ||
tm, err := time.Parse(time.RFC3339Nano, *manifest.PublishTime) | ||
if err != nil { | ||
return &core.Report{ | ||
Name: "PresentationDelayInspector", | ||
Severity: core.Error, | ||
Message: "invalid MPD@publishTime", | ||
Values: core.Values{"error": err}, | ||
} | ||
} | ||
wallClock = tm | ||
} | ||
|
||
// get suggestedPresentationDelay | ||
var suggestedPresentationDelay time.Duration | ||
if manifest.SuggestedPresentationDelay != nil { | ||
suggestedPresentationDelay = time.Duration(*manifest.SuggestedPresentationDelay) | ||
} | ||
|
||
// get availabilityStartTime | ||
var availabilityStartTime time.Time | ||
if manifest.AvailabilityStartTime != nil { | ||
tm, err := time.Parse(time.RFC3339Nano, *manifest.AvailabilityStartTime) | ||
if err != nil { | ||
return &core.Report{ | ||
Name: "PresentationDelayInspector", | ||
Severity: core.Error, | ||
Message: "invalid MPD@availabilityStartTime", | ||
Values: core.Values{"error": err}, | ||
} | ||
} | ||
availabilityStartTime = tm | ||
} | ||
|
||
// get latest time | ||
var earliestVideoTime time.Time | ||
var latestVideoTime time.Time | ||
err := manifest.EachSegments(func(segment *core.DASHSegment) (cont bool) { | ||
if segment.Initialization { | ||
return true | ||
} | ||
var periodStart float64 | ||
if segment.Period.Start != nil { | ||
periodStart = time.Duration(*segment.Period.Start).Seconds() | ||
} | ||
var offset uint64 | ||
if segment.SegmentTemplate.PresentationTimeOffset != nil { | ||
offset = *segment.SegmentTemplate.PresentationTimeOffset | ||
} | ||
timescale := float64(1) | ||
if segment.SegmentTemplate.Timescale != nil { | ||
timescale = float64(*segment.SegmentTemplate.Timescale) | ||
} | ||
s := int64(segment.Time) - int64(offset) | ||
fs := periodStart + float64(s)/timescale | ||
ts := availabilityStartTime.Add(time.Duration(fs*1e9) * time.Nanosecond) | ||
if earliestVideoTime.IsZero() || ts.Before(earliestVideoTime) { | ||
earliestVideoTime = ts | ||
} | ||
e := s + int64(segment.Duration) | ||
fe := periodStart + float64(e)/timescale | ||
te := availabilityStartTime.Add(time.Duration(fe*1e9) * time.Nanosecond) | ||
if te.After(latestVideoTime) { | ||
latestVideoTime = te | ||
} | ||
return true | ||
}) | ||
if err != nil { | ||
return &core.Report{ | ||
Name: "PresentationDelayInspector", | ||
Severity: core.Error, | ||
Message: "unexpected error", | ||
Values: core.Values{"error": err}, | ||
} | ||
} | ||
|
||
values := core.Values{ | ||
"earliestVideoTime": earliestVideoTime.UTC().Format(time.RFC3339Nano), | ||
"latestVideoTime": latestVideoTime.UTC().Format(time.RFC3339Nano), | ||
"wallClock": wallClock.UTC().Format(time.RFC3339Nano), | ||
"suggestedPresentationDelay": suggestedPresentationDelay, | ||
} | ||
earliestRenderTime := earliestVideoTime.Add(suggestedPresentationDelay) | ||
latestRenderTime := latestVideoTime.Add(suggestedPresentationDelay) | ||
if earliestRenderTime.Add(ins.config.Error).After(wallClock) { | ||
return &core.Report{ | ||
Name: "PresentationDelayInspector", | ||
Severity: core.Error, | ||
Message: "earliest segment is out of suggested time range", | ||
Values: values, | ||
} | ||
} else if earliestRenderTime.Add(ins.config.Warn).After(wallClock) { | ||
return &core.Report{ | ||
Name: "PresentationDelayInspector", | ||
Severity: core.Warn, | ||
Message: "earliest segment is out of suggested time range", | ||
Values: values, | ||
} | ||
} | ||
if latestRenderTime.Add(-ins.config.Error).Before(wallClock) { | ||
return &core.Report{ | ||
Name: "PresentationDelayInspector", | ||
Severity: core.Error, | ||
Message: "latest segment is out of suggested time range", | ||
Values: values, | ||
} | ||
} else if latestRenderTime.Add(-ins.config.Warn).Before(wallClock) { | ||
return &core.Report{ | ||
Name: "PresentationDelayInspector", | ||
Severity: core.Warn, | ||
Message: "latest segment is out of suggested time range", | ||
Values: values, | ||
} | ||
} | ||
return &core.Report{ | ||
Name: "PresentationDelayInspector", | ||
Severity: core.Info, | ||
Message: "good", | ||
Values: values, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
package dash | ||
|
||
import ( | ||
"testing" | ||
"time" | ||
|
||
"github.com/abema/antares/core" | ||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
"github.com/zencoder/go-dash/helpers/ptrs" | ||
"github.com/zencoder/go-dash/mpd" | ||
) | ||
|
||
func TestPresentationDelayInspector(t *testing.T) { | ||
ins := NewPresentationDelayInspector() | ||
|
||
buildManifest := func(suggestedPresentationDelay time.Duration) *core.Manifest { | ||
periodStart := 5 * time.Hour | ||
return &core.Manifest{ | ||
MPD: &mpd.MPD{ | ||
Type: ptrs.Strptr("dynamic"), | ||
AvailabilityStartTime: ptrs.Strptr("2023-01-01T00:00:00Z"), | ||
SuggestedPresentationDelay: (*mpd.Duration)(&suggestedPresentationDelay), | ||
Periods: []*mpd.Period{{ | ||
Start: (*mpd.Duration)(&periodStart), | ||
AdaptationSets: []*mpd.AdaptationSet{{ | ||
SegmentTemplate: &mpd.SegmentTemplate{ | ||
Timescale: ptrs.Int64ptr(90000), | ||
PresentationTimeOffset: ptrs.Uint64ptr(10000 * 90000), | ||
Media: ptrs.Strptr("$Time$.mp4"), | ||
SegmentTimeline: &mpd.SegmentTimeline{ | ||
Segments: []*mpd.SegmentTimelineSegment{ | ||
{ | ||
StartTime: ptrs.Uint64ptr(13600 * 90000), | ||
Duration: 4 * 90000, | ||
RepeatCount: ptrs.Intptr(7), | ||
}, | ||
}, | ||
}, | ||
}, | ||
Representations: []*mpd.Representation{{}}, | ||
}}, | ||
}}, | ||
UTCTiming: &mpd.DescriptorType{ | ||
SchemeIDURI: ptrs.Strptr("urn:mpeg:dash:utc:direct:2014"), | ||
Value: ptrs.Strptr("2023-01-01T06:00:36Z"), // 4 seconds later from latest segment | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
t.Run("ok", func(t *testing.T) { | ||
report := ins.Inspect(buildManifest(7*time.Second), nil) | ||
require.Equal(t, core.Info, report.Severity) | ||
assert.Equal(t, "good", report.Message) | ||
}) | ||
|
||
t.Run("warn/presentation_time_is_new", func(t *testing.T) { | ||
report := ins.Inspect(buildManifest(5*time.Second), nil) | ||
require.Equal(t, core.Warn, report.Severity) | ||
assert.Equal(t, "latest segment is out of suggested time range", report.Message) | ||
}) | ||
|
||
t.Run("error/presentation_time_is_new", func(t *testing.T) { | ||
report := ins.Inspect(buildManifest(3*time.Second), nil) | ||
require.Equal(t, core.Error, report.Severity) | ||
assert.Equal(t, "latest segment is out of suggested time range", report.Message) | ||
}) | ||
|
||
t.Run("warn/presentation_time_is_old", func(t *testing.T) { | ||
report := ins.Inspect(buildManifest(35*time.Second), nil) | ||
require.Equal(t, core.Warn, report.Severity) | ||
assert.Equal(t, "earliest segment is out of suggested time range", report.Message) | ||
}) | ||
|
||
t.Run("error/presentation_time_is_old", func(t *testing.T) { | ||
report := ins.Inspect(buildManifest(37*time.Second), nil) | ||
require.Equal(t, core.Error, report.Severity) | ||
assert.Equal(t, "earliest segment is out of suggested time range", report.Message) | ||
}) | ||
|
||
t.Run("skip_vod_manifest", func(t *testing.T) { | ||
report := ins.Inspect(&core.Manifest{MPD: &mpd.MPD{Type: ptrs.Strptr("static")}}, nil) | ||
require.Equal(t, core.Info, report.Severity) | ||
assert.Equal(t, "skip VOD manifest", report.Message) | ||
}) | ||
|
||
t.Run("invalid_utc_timing", func(t *testing.T) { | ||
report := ins.Inspect(&core.Manifest{ | ||
MPD: &mpd.MPD{ | ||
Type: ptrs.Strptr("dynamic"), | ||
UTCTiming: &mpd.DescriptorType{ | ||
SchemeIDURI: ptrs.Strptr("urn:mpeg:dash:utc:direct:2014"), | ||
Value: ptrs.Strptr("xxxxx"), | ||
}, | ||
}, | ||
}, nil) | ||
require.Equal(t, core.Info, report.Severity) | ||
assert.Equal(t, "skip VOD manifest", report.Message) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters