Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for utc_head #75

Merged
merged 2 commits into from
Aug 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading