Skip to content

Commit

Permalink
Merge pull request #75 from Dash-Industry-Forum/utc-head
Browse files Browse the repository at this point in the history
feat: add support for utc_head
  • Loading branch information
tobbee authored Aug 22, 2023
2 parents e8d0472 + 560902f commit 1937bfd
Show file tree
Hide file tree
Showing 12 changed files with 51 additions and 49 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed

- moved list URL parameters to [livesim2 wiki](https://github.com/Dash-Industry-Forum/livesim2/wiki/URL-Parameters)
- removed `scheme` and `urlprefix` configuration. Now replaced with `host` which overrides `scheme://host` in all generated URLs

### Added

Expand Down
9 changes: 2 additions & 7 deletions cmd/livesim2/app/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,8 @@ type ServerConfig struct {
CertPath string `json:"certpath"`
// KeyPath is a path to a valid private TLS key
KeyPath string `json:"keypath"`
// Scheme should be http or https if set. Otherwise the scheme is auto-detected as far as possible.
Scheme string `json:"scheme"`
// If Host is set, it will be used instead of autodetected value.
// If Host is set, it will be used instead of autodetected value scheme://host.
Host string `json:"host"`
// URLPrefix is used for paths generated by livesim2, such as links in welcome page
URLPrefix string `json:"urlprefix"`
}

var DefaultConfig = ServerConfig{
Expand Down Expand Up @@ -113,8 +109,7 @@ func LoadConfig(args []string, cwd string) (*ServerConfig, error) {
f.String("certpath", k.String("certpath"), "path to TLS certificate file (for HTTPS). Use domains instead if possible")
f.String("keypath", k.String("keypath"), "path to TLS private key file (for HTTPS). Use domains instead if possible.")
f.String("scheme", k.String("scheme"), "scheme used in Location and BaseURL elements. If empty, it is attempted to be auto-detected")
f.String("host", k.String("host"), "host used in Location and BaseURL elements. If empty, it is attempted to be auto-detected")
f.String("urlprefix", k.String("urlprefix"), "prefix when paths are not mounted at root")
f.String("host", k.String("host"), "host (and possible prefix) used in MPD elements. Overrides auto-detected full scheme://host")
if err := f.Parse(args[1:]); err != nil {
return nil, fmt.Errorf("command line parse: %w", err)
}
Expand Down
3 changes: 2 additions & 1 deletion cmd/livesim2/app/configurl.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ type UTCTimingMethod string

const (
UtcTimingDirect UTCTimingMethod = "direct"
UtcTimingHead UTCTimingMethod = "head" // Note, not supported
UtcTimingHead UTCTimingMethod = "head"
UtcTimingNtp UTCTimingMethod = "ntp"
UtcTimingSntp UTCTimingMethod = "sntp"
UtcTimingHttpXSDate UTCTimingMethod = "httpxsdate"
Expand All @@ -42,6 +42,7 @@ const (
UtcTimingSntpServer = "time.kfki.hu"
UtcTimingHttpServer = "http://time.akamai.com/?iso"
UtcTimingHttpServerMS = "http://time.akamai.com/?isoms"
UtcTimingHeadAsset = "/static/time.txt"
)

type ResponseConfig struct {
Expand Down
19 changes: 3 additions & 16 deletions cmd/livesim2/app/configurl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,18 @@ func TestProcessURLCfg(t *testing.T) {
err string
}{
{
url: "/livesim2/utc_direct-ntp-sntp-httpxsdate-httpiso/asset/x.mpd",
url: "/livesim2/utc_direct-ntp-sntp-httpxsdate-httpiso-head/asset/x.mpd",
nowMS: 0,
contentPart: "asset/x.mpd",
wantedCfg: &ResponseConfig{
URLParts: []string{"", "livesim2", "utc_direct-ntp-sntp-httpxsdate-httpiso", "asset", "x.mpd"},
URLParts: []string{"", "livesim2", "utc_direct-ntp-sntp-httpxsdate-httpiso-head", "asset", "x.mpd"},
URLContentIdx: 3,
StartTimeS: 0,
TimeShiftBufferDepthS: Ptr(defaultTimeShiftBufferDepthS),
StartNr: Ptr(0),
AvailabilityTimeCompleteFlag: true,
TimeSubsDurMS: defaultTimeSubsDurMS,
UTCTimingMethods: []UTCTimingMethod{"direct", "ntp", "sntp", "httpxsdate", "httpiso"},
UTCTimingMethods: []UTCTimingMethod{"direct", "ntp", "sntp", "httpxsdate", "httpiso", "head"},
},
err: "",
},
Expand All @@ -48,19 +48,6 @@ func TestProcessURLCfg(t *testing.T) {
},
err: `key="utc", val="unknown" is not a valid UTC timing method`,
},
{
url: "/livesim/utc_head/asset.mpd",
nowMS: 0,
contentPart: "asset.mpd",
wantedCfg: &ResponseConfig{
StartTimeS: 0,
TimeShiftBufferDepthS: Ptr(defaultTimeShiftBufferDepthS),
StartNr: Ptr(0),
AvailabilityTimeCompleteFlag: true,
TimeSubsDurMS: defaultTimeSubsDurMS,
},
err: `key="utc", val="head", UTC timing method "head" not supported`,
},
{
url: "/livesim2/utc_none/asset.mpd",
nowMS: 0,
Expand Down
3 changes: 2 additions & 1 deletion cmd/livesim2/app/handler_index.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import (
// indexHandlerFunc handles access to /.
func (s *Server) indexHandlerFunc(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
err := s.htmlTemplates.ExecuteTemplate(w, "welcome.html", s.Cfg.URLPrefix)
fullHost := getSchemeAndHost(r, s.Cfg)
err := s.htmlTemplates.ExecuteTemplate(w, "welcome.html", fullHost)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
Expand Down
11 changes: 6 additions & 5 deletions cmd/livesim2/app/handler_index_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package app

import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"strings"
Expand All @@ -15,12 +16,12 @@ import (
"github.com/stretchr/testify/require"
)

func TestIndexPageWithPrefix(t *testing.T) {
func TestIndexPageWithHost(t *testing.T) {
cfg := ServerConfig{
VodRoot: "testdata/assets",
TimeoutS: 0,
LogFormat: logging.LogDiscard,
URLPrefix: "/livesim2",
Host: "https://example.com/subfolder",
}
_, err := logging.InitZerolog(cfg.LogLevel, cfg.LogFormat)
require.NoError(t, err)
Expand All @@ -31,10 +32,10 @@ func TestIndexPageWithPrefix(t *testing.T) {

resp, body := testFullRequest(t, ts, "GET", "/", nil)
require.Equal(t, http.StatusOK, resp.StatusCode)
require.Greater(t, strings.Index(string(body), `href="/livesim2/assets"`), 0)
require.Greater(t, strings.Index(string(body), `href="https://example.com/subfolder/assets"`), 0)
}

func TestIndexPageWithoutPrefix(t *testing.T) {
func TestIndexPageWithoutHost(t *testing.T) {
cfg := ServerConfig{
VodRoot: "testdata/assets",
TimeoutS: 0,
Expand All @@ -49,5 +50,5 @@ func TestIndexPageWithoutPrefix(t *testing.T) {

resp, body := testFullRequest(t, ts, "GET", "/", nil)
require.Equal(t, http.StatusOK, resp.StatusCode)
require.Greater(t, strings.Index(string(body), `href="/assets"`), 0)
require.Greater(t, strings.Index(string(body), fmt.Sprintf(`href="%s/assets"`, ts.URL)), 0)
}
19 changes: 15 additions & 4 deletions cmd/livesim2/app/handler_livesim.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ func (s *Server) livesimHandlerFunc(w http.ResponseWriter, r *http.Request) {
http.Error(w, msg, http.StatusInternalServerError)
return
}
fullHost := getSchemeAndHost(r, s.Cfg)

var nowMS int // Set from query string or from wall-clock
q := r.URL.Query()
Expand Down Expand Up @@ -78,9 +79,8 @@ func (s *Server) livesimHandlerFunc(w http.ResponseWriter, r *http.Request) {
switch ext {
case ".mpd":
_, mpdName := path.Split(contentPart)
cfg.SetScheme(s.Cfg.Scheme, r)
cfg.SetHost(s.Cfg.Host, r)
err := writeLiveMPD(log, w, cfg, a, mpdName, nowMS)
err := writeLiveMPD(log, w, cfg, a, mpdName, fullHost, nowMS)
if err != nil {
// TODO. Add more granular errors like 404 not found
msg := fmt.Sprintf("liveMPD: %s", err)
Expand Down Expand Up @@ -112,10 +112,21 @@ func (s *Server) livesimHandlerFunc(w http.ResponseWriter, r *http.Request) {
}
}

func writeLiveMPD(log *zerolog.Logger, w http.ResponseWriter, cfg *ResponseConfig, a *asset, mpdName string, nowMS int) error {
func getSchemeAndHost(r *http.Request, cfg *ServerConfig) string {
if cfg.Host != "" {
return cfg.Host
}
scheme := "http"
if r.TLS != nil {
scheme = "https"
}
return fmt.Sprintf("%s://%s", scheme, r.Host)
}

func writeLiveMPD(log *zerolog.Logger, w http.ResponseWriter, cfg *ResponseConfig, a *asset, mpdName, host string, nowMS int) error {
work := make([]byte, 0, 1024)
buf := bytes.NewBuffer(work)
lMPD, err := LiveMPD(a, mpdName, cfg, nowMS)
lMPD, err := LiveMPD(a, mpdName, cfg, host, nowMS)
if err != nil {
return fmt.Errorf("convertToLive: %w", err)
}
Expand Down
11 changes: 8 additions & 3 deletions cmd/livesim2/app/livempd.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func calcWrapTimes(a *asset, cfg *ResponseConfig, nowMS int, tsbd m.Duration) wr
}

// LiveMPD generates a dynamic configured MPD for a VoD asset.
func LiveMPD(a *asset, mpdName string, cfg *ResponseConfig, nowMS int) (*m.MPD, error) {
func LiveMPD(a *asset, mpdName string, cfg *ResponseConfig, host string, nowMS int) (*m.MPD, error) {
mpd, err := a.getVodMPD(mpdName)
if err != nil {
return nil, err
Expand Down Expand Up @@ -90,7 +90,7 @@ func LiveMPD(a *asset, mpdName string, cfg *ResponseConfig, nowMS int) (*m.MPD,
}
}

addUTCTimings(mpd, cfg)
addUTCTimings(mpd, cfg, host)

afterStop := false
endTimeMS := nowMS
Expand Down Expand Up @@ -525,7 +525,7 @@ func lastSegAvailTimeS(cfg *ResponseConfig, lsi lastSegInfo) float64 {
}

// addUTCTimings adds the UTCTiming elements to the MPD.
func addUTCTimings(mpd *m.MPD, cfg *ResponseConfig) {
func addUTCTimings(mpd *m.MPD, cfg *ResponseConfig, host string) {
if len(cfg.UTCTimingMethods) == 0 {
// default if none is set. Use HTTP with ms precision.
mpd.UTCTimings = []*m.DescriptorType{
Expand Down Expand Up @@ -564,6 +564,11 @@ func addUTCTimings(mpd *m.MPD, cfg *ResponseConfig) {
SchemeIdUri: "urn:mpeg:dash:utc:http-iso:2014",
Value: UtcTimingHttpServerMS,
}
case UtcTimingHead:
ut = &m.DescriptorType{
SchemeIdUri: "urn:mpeg:dash:utc:http-head:2014",
Value: fmt.Sprintf("%s%s", host, UtcTimingHeadAsset),
}
case UtcTimingNone:
cfg.UTCTimingMethods = nil
return // no UTCTiming elements
Expand Down
18 changes: 9 additions & 9 deletions cmd/livesim2/app/livempd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ func TestLiveMPD(t *testing.T) {
cfg := NewResponseConfig()
nowMS := 100_000
// Number template
liveMPD, err := LiveMPD(asset, tc.mpdName, cfg, nowMS)
liveMPD, err := LiveMPD(asset, tc.mpdName, cfg, "", nowMS)
assert.NoError(t, err)
assert.Equal(t, "dynamic", *liveMPD.Type)
assert.Equal(t, m.DateTime("1970-01-01T00:00:00Z"), liveMPD.AvailabilityStartTime)
Expand All @@ -83,7 +83,7 @@ func TestLiveMPD(t *testing.T) {
}
// SegmentTimeline with $Time$
cfg.SegTimelineFlag = true
liveMPD, err = LiveMPD(asset, tc.mpdName, cfg, nowMS)
liveMPD, err = LiveMPD(asset, tc.mpdName, cfg, "", nowMS)
assert.NoError(t, err)
assert.Equal(t, "dynamic", *liveMPD.Type)
assert.Equal(t, m.DateTime("1970-01-01T00:00:00Z"), liveMPD.AvailabilityStartTime)
Expand Down Expand Up @@ -133,7 +133,7 @@ func TestLiveMPDWithTimeSubs(t *testing.T) {
cfg.TimeSubsStpp = []string{"en", "sv"}
nowMS := 100_000
// Number template
liveMPD, err := LiveMPD(asset, tc.mpdName, cfg, nowMS)
liveMPD, err := LiveMPD(asset, tc.mpdName, cfg, "", nowMS)
assert.NoError(t, err)
assert.Equal(t, "dynamic", *liveMPD.Type)
aSets := liveMPD.Periods[0].AdaptationSets
Expand Down Expand Up @@ -208,7 +208,7 @@ func TestSegmentTimes(t *testing.T) {
}
for nowS := tc.startTimeS; nowS < tc.endTimeS; nowS++ {
nowMS := nowS * 1000
liveMPD, err := LiveMPD(asset, tc.mpdName, cfg, nowMS)
liveMPD, err := LiveMPD(asset, tc.mpdName, cfg, "", nowMS)
wantedStartNr := (nowS - 62) / 2 // Sliding window of 60s + one segment
assert.NoError(t, err)
for _, as := range liveMPD.Periods[0].AdaptationSets {
Expand Down Expand Up @@ -460,7 +460,7 @@ func TestPublishTime(t *testing.T) {
}
err := verifyAndFillConfig(cfg, tc.nowMS)
require.NoError(t, err)
liveMPD, err := LiveMPD(asset, tc.mpdName, cfg, tc.nowMS)
liveMPD, err := LiveMPD(asset, tc.mpdName, cfg, "", tc.nowMS)
assert.NoError(t, err)
assert.Equal(t, m.ConvertToDateTimeS(int64(tc.availabilityStartTime)), liveMPD.AvailabilityStartTime)
assert.Equal(t, m.DateTime(tc.wantedPublishTime), liveMPD.PublishTime)
Expand Down Expand Up @@ -532,7 +532,7 @@ func TestNormalAvailabilityTimeOffset(t *testing.T) {
cfg.SegTimelineFlag = tc.segTimelineTime
sc := strConvAccErr{}
cfg.AvailabilityTimeOffsetS = sc.AtofInf("ato", tc.ato)
liveMPD, err := LiveMPD(asset, tc.mpdName, cfg, tc.nowMS)
liveMPD, err := LiveMPD(asset, tc.mpdName, cfg, "", tc.nowMS)
if tc.wantedErr != "" {
assert.EqualError(t, err, tc.wantedErr)
return
Expand Down Expand Up @@ -602,7 +602,7 @@ func TestUTCTiming(t *testing.T) {
}
err := verifyAndFillConfig(cfg, tc.nowMS)
require.NoError(t, err)
liveMPD, err := LiveMPD(asset, tc.mpdName, cfg, tc.nowMS)
liveMPD, err := LiveMPD(asset, tc.mpdName, cfg, "", tc.nowMS)
assert.NoError(t, err)
assert.Equal(t, m.DateTime(tc.wantedPublishTime), liveMPD.PublishTime)
assert.Equal(t, tc.wantedUTCTimings, len(liveMPD.UTCTimings))
Expand Down Expand Up @@ -700,7 +700,7 @@ func TestMultiPeriod(t *testing.T) {
default: // $Number$
// no flag
}
liveMPD, err := LiveMPD(asset, tc.mpdName, cfg, tc.nowMS)
liveMPD, err := LiveMPD(asset, tc.mpdName, cfg, "", tc.nowMS)
if tc.wantedErr != "" {
assert.EqualError(t, err, tc.wantedErr)
return
Expand Down Expand Up @@ -754,7 +754,7 @@ func TestRelStartStopTimeIntoLocation(t *testing.T) {
asset, ok := am.findAsset(contentPart)
require.True(t, ok)
_, mpdName := path.Split(contentPart)
liveMPD, err := LiveMPD(asset, mpdName, cfg, c.nowMS)
liveMPD, err := LiveMPD(asset, mpdName, cfg, "", c.nowMS)
require.NoError(t, err)
require.Equal(t, c.wantedLocation, string(liveMPD.Location[0]), "the right location element is not inserted")
}
Expand Down
1 change: 1 addition & 0 deletions cmd/livesim2/app/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ func (s *Server) Routes(ctx context.Context) error {
s.Router.MethodFunc("GET", "/config", s.configHandlerFunc)
s.Router.MethodFunc("GET", "/assets", s.assetsHandlerFunc)
s.Router.MethodFunc("GET", "/static/*", s.embeddedStaticHandlerFunc)
s.Router.MethodFunc("HEAD", "/static/*", s.embeddedStaticHandlerFunc)
// LiveRouter is mounted at /livesim2
s.LiveRouter.MethodFunc("GET", "/*", s.livesimHandlerFunc)
s.LiveRouter.MethodFunc("HEAD", "/*", s.livesimHandlerFunc)
Expand Down
1 change: 1 addition & 0 deletions cmd/livesim2/app/static/time.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Used for UTCTiming in head mode.
4 changes: 1 addition & 3 deletions cmd/livesim2/app/strconv.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,8 @@ func (s *strConvAccErr) SplitUTCTimings(key, val string) []UTCTimingMethod {
utcVal := UTCTimingMethod(val)
switch utcVal {
case UtcTimingDirect, UtcTimingNtp, UtcTimingSntp, UtcTimingHttpXSDate, UtcTimingHttpISO,
UtcTimingNone:
UtcTimingNone, UtcTimingHead:
utcTimingMethods[i] = utcVal
case UtcTimingHead:
s.err = fmt.Errorf("key=%q, val=%q, UTC timing method %q not supported", key, val, UtcTimingHead)
default:
s.err = fmt.Errorf("key=%q, val=%q is not a valid UTC timing method", key, val)
}
Expand Down

0 comments on commit 1937bfd

Please sign in to comment.