diff --git a/cmd/livesim2/app/asset.go b/cmd/livesim2/app/asset.go index 9d53650..201ae6c 100644 --- a/cmd/livesim2/app/asset.go +++ b/cmd/livesim2/app/asset.go @@ -38,7 +38,7 @@ func newAssetMgr(vodFS fs.FS, repDataDir string, writeRepData bool) *assetMgr { type assetMgr struct { vodFS fs.FS - assets map[string]*asset + assets map[string]*asset // the key is the asset path repDataDir string writeRepData bool } @@ -100,7 +100,6 @@ func (am *assetMgr) loadAsset(mpdPath string) error { return fmt.Errorf("read MPD: %w", err) } md.MPDStr = string(data) - asset.MPDs[mpdName] = md mpd, err := m.ReadFromString(md.MPDStr) if err != nil { @@ -115,6 +114,15 @@ func (am *assetMgr) loadAsset(mpdPath string) error { return fmt.Errorf("mpd type is not static") } + if len(mpd.ProgramInformation) > 0 { + pi := mpd.ProgramInformation[0] + if pi.Title != "" { + md.Title = pi.Title + } + } + md.Dur = mpd.MediaPresentationDuration.String() + asset.MPDs[mpdName] = md + for _, as := range mpd.Periods[0].AdaptationSets { if as.SegmentTemplate == nil { return fmt.Errorf("no SegmentTemplate in adaptation set") diff --git a/cmd/livesim2/app/handler_assets.go b/cmd/livesim2/app/handler_assets.go index 90acc41..09f4ca3 100644 --- a/cmd/livesim2/app/handler_assets.go +++ b/cmd/livesim2/app/handler_assets.go @@ -5,18 +5,31 @@ package app import ( - "encoding/json" "net/http" - "path" "sort" + "strings" ) -type assetsInfo struct { - MPDs []string +type AssetsInfo struct { + Host string + Assets []*AssetInfo +} + +type AssetInfo struct { + Path string + LoopDurMS int + MPDs []MPDInfo +} + +type MPDInfo struct { + Path string + Desc string + Dur string } // assetHandlerFunc returns information about assets func (s *Server) assetsHandlerFunc(w http.ResponseWriter, r *http.Request) { + forVod := strings.HasPrefix(r.URL.String(), "/vod") assets := make([]*asset, 0, len(s.assetMgr.assets)) for _, a := range s.assetMgr.assets { assets = append(assets, a) @@ -24,20 +37,36 @@ func (s *Server) assetsHandlerFunc(w http.ResponseWriter, r *http.Request) { sort.Slice(assets, func(i, j int) bool { return assets[i].AssetPath < assets[j].AssetPath }) - aInfo := assetsInfo{} - mpds := make([]string, 0, len(assets)) + aInfo := AssetsInfo{ + Host: fullHost(s.Cfg.Host, r), + Assets: make([]*AssetInfo, 0, len(assets)), + } for _, asset := range assets { - for m := range asset.MPDs { - fullURL := path.Join(asset.AssetPath, m) - mpds = append(mpds, fullURL) + mpds := make([]MPDInfo, 0, len(asset.MPDs)) + for _, mpd := range asset.MPDs { + mpds = append(mpds, MPDInfo{ + Path: mpd.Name, + Desc: mpd.Title, + Dur: mpd.Dur, + }) + } + sort.Slice(mpds, func(i, j int) bool { + return mpds[i].Path < mpds[j].Path + }) + assetInfo := AssetInfo{ + Path: asset.AssetPath, + LoopDurMS: asset.LoopDurMS, + MPDs: mpds, } + aInfo.Assets = append(aInfo.Assets, &assetInfo) + } + w.Header().Set("Content-Type", "text/html") + templateName := "assets.html" + if forVod { + templateName = "assets_vod.html" } - sort.Strings(mpds) - aInfo.MPDs = append(aInfo.MPDs, mpds...) - body, err := json.MarshalIndent(aInfo, "", " ") + err := s.htmlTemplates.ExecuteTemplate(w, templateName, aInfo) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } - w.Header().Set("Content-Type", "application/json") - _, _ = w.Write(body) } diff --git a/cmd/livesim2/app/handler_index.go b/cmd/livesim2/app/handler_index.go index 90915ed..ef5265b 100644 --- a/cmd/livesim2/app/handler_index.go +++ b/cmd/livesim2/app/handler_index.go @@ -8,12 +8,20 @@ import ( "io/fs" "net/http" "strconv" + + "github.com/Dash-Industry-Forum/livesim2/internal" ) +type welcomeInfo struct { + Host string + Version string +} + // 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", fullHost(s.Cfg.Host, r)) + wi := welcomeInfo{Host: fullHost(s.Cfg.Host, r), Version: internal.GetVersion()} + err := s.htmlTemplates.ExecuteTemplate(w, "welcome.html", wi) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } diff --git a/cmd/livesim2/app/routes.go b/cmd/livesim2/app/routes.go index 213759e..fdbc803 100644 --- a/cmd/livesim2/app/routes.go +++ b/cmd/livesim2/app/routes.go @@ -29,6 +29,7 @@ func (s *Server) Routes(ctx context.Context) error { s.Router.MethodFunc("GET", "/favicon.ico", s.favIconFunc) s.Router.MethodFunc("GET", "/config", s.configHandlerFunc) s.Router.MethodFunc("GET", "/assets", s.assetsHandlerFunc) + s.Router.MethodFunc("GET", "/vod", s.assetsHandlerFunc) s.Router.MethodFunc("GET", "/static/*", s.embeddedStaticHandlerFunc) s.Router.MethodFunc("HEAD", "/static/*", s.embeddedStaticHandlerFunc) // LiveRouter is mounted at /livesim2 diff --git a/cmd/livesim2/app/templates/assets_vod.html b/cmd/livesim2/app/templates/assets_vod.html new file mode 100644 index 0000000..8c2e50d --- /dev/null +++ b/cmd/livesim2/app/templates/assets_vod.html @@ -0,0 +1,37 @@ + + + + + + + Local livesim2 assets + + +
+ {{$h := .Host}} +
+

Available livesim2 VoD assets!

+

host={{$h}}

+
+

The following is a list of assets and MPDs of these assets.
+ They can be streamed with URLs like {{(print $h "/vod//")}}

+ + {{range $a := .Assets}} +
+

{{$a.Path}}

+ + + {{range $m := $a.MPDs}} + + {{$url := (print $h "/vod/" $a.Path "/" $m.Path)}} + + + + + {{end}} +
MPD URLDescriptionDuration
{{$m.Path}}{{$m.Desc}}{{$m.Dur}}
+
+ {{end}} +
+ + \ No newline at end of file diff --git a/cmd/livesim2/app/templates/welcome.html b/cmd/livesim2/app/templates/welcome.html index 6eaae94..ae8b020 100644 --- a/cmd/livesim2/app/templates/welcome.html +++ b/cmd/livesim2/app/templates/welcome.html @@ -1,25 +1,51 @@ - + - - - - - Welcome to livesim2 - - - -

Welcome to livesim2

- - - - - - - - - - - -
The following URLs should work on this server
/assets to get a list of assets (each can have multiple MPDs)
{{.}}/vod/... to stream any of the VoD assets directly
{{.}}/livesim2/... to stream the VoD assets as they are converted to "live" with parameters
/healthz to check if server is running
/config to get the current config of the server
/metrics to get Prometheus metrics for the system and all streaming content requests
/static/features.html compares features between livesim2 and livesim1
livesim2-wiki compares url parameters between livesim2 and livesim1
- + + + + + livesim2 + + +
+
+

Welcome to livesim2!

+

Running at {{.}}

+
+ + +

livesim2 is a simulator for MPEG-DASH live streaming developed by DASH-IF. It uses VoD assets + and converts them to wall-clock synchronized "live" streams, by rewriting + timestamps, segment numbers, and segment URLs.

+ +

In addition, a plethora of properties of the stream can be changed by specifying + parameters in the URLs.

+ +

Local URLs

+ + The following URLs should work at {{.Host}}:
+ + +

Further information

+ + For more information about the project, see + + + +

Version: {{.Version}}

+
+ diff --git a/cmd/livesim2/app/templates_test.go b/cmd/livesim2/app/templates_test.go index ed926f3..25ce21c 100644 --- a/cmd/livesim2/app/templates_test.go +++ b/cmd/livesim2/app/templates_test.go @@ -72,10 +72,11 @@ func TestHTMLTemplates(t *testing.T) { templateRoot := os.DirFS("templates") textTemplates, err := compileHTMLTemplates(templateRoot, "") require.NoError(t, err) - require.Equal(t, `; defined templates are: "welcome.html"`, textTemplates.DefinedTemplates()) + require.Equal(t, 3, len(textTemplates.Templates())) var buf bytes.Buffer - err = textTemplates.ExecuteTemplate(&buf, "welcome.html", "/prefix") + wi := welcomeInfo{Host: "http://localhost:8888", Version: "1.2.3"} + err = textTemplates.ExecuteTemplate(&buf, "welcome.html", wi) require.NoError(t, err) welcomeStr := buf.String() - require.Greater(t, strings.Index(welcomeStr, `href="/prefix/assets"`), 0) + require.Greater(t, strings.Index(welcomeStr, `href="http://localhost:8888/assets"`), 0) } diff --git a/cmd/livesim2/app/testdata/assets/testpic_2s/Manifest.mpd b/cmd/livesim2/app/testdata/assets/testpic_2s/Manifest.mpd index c0fa084..4793f05 100644 --- a/cmd/livesim2/app/testdata/assets/testpic_2s/Manifest.mpd +++ b/cmd/livesim2/app/testdata/assets/testpic_2s/Manifest.mpd @@ -1,7 +1,7 @@ - Media Presentation Description from DASH-IF live simulator 2 + 640x360@30 video, 48kHz audio, 2s segments diff --git a/cmd/livesim2/app/testdata/assets/testpic_2s/Manifest_imsc1.mpd b/cmd/livesim2/app/testdata/assets/testpic_2s/Manifest_imsc1.mpd index e18096d..efef96c 100644 --- a/cmd/livesim2/app/testdata/assets/testpic_2s/Manifest_imsc1.mpd +++ b/cmd/livesim2/app/testdata/assets/testpic_2s/Manifest_imsc1.mpd @@ -1,7 +1,7 @@ - Media Presentation Description from DASH-IF live simulator 2 + 640x360@30 video, 48kHz audio, two imsc1 subtitle tracks, 2s segments diff --git a/cmd/livesim2/app/testdata/assets/testpic_2s/Manifest_thumbs.mpd b/cmd/livesim2/app/testdata/assets/testpic_2s/Manifest_thumbs.mpd index fb45bd5..d31ec61 100644 --- a/cmd/livesim2/app/testdata/assets/testpic_2s/Manifest_thumbs.mpd +++ b/cmd/livesim2/app/testdata/assets/testpic_2s/Manifest_thumbs.mpd @@ -1,7 +1,7 @@ - + - Media Presentation Description from DASH-IF live simulator + 640x360@30 video, 48kHz audio, DASH-IF thumnail track, 2s segments diff --git a/cmd/livesim2/app/testdata/assets/testpic_8s/Manifest.mpd b/cmd/livesim2/app/testdata/assets/testpic_8s/Manifest.mpd index ecb0a5b..9bd2133 100644 --- a/cmd/livesim2/app/testdata/assets/testpic_8s/Manifest.mpd +++ b/cmd/livesim2/app/testdata/assets/testpic_8s/Manifest.mpd @@ -1,5 +1,8 @@ + + 640x360@30 video, 48kHz audio, 8s segments + diff --git a/internal/mpddata.go b/internal/mpddata.go index dfbb4ac..12c0c4b 100644 --- a/internal/mpddata.go +++ b/internal/mpddata.go @@ -19,7 +19,10 @@ const ( type MPDData struct { Name string `json:"name"` OrigURI string `json:"originURI"` - MPDStr string `json:"-"` + Title string `json:"-"` + // Dur is MediaPresentationDuration + Dur string `json:"-"` + MPDStr string `json:"-"` } // WriteMPDData to file on disk. @@ -41,7 +44,7 @@ func WriteMPDData(dirPath string, name, uri string) error { return err } } - mpds = append(mpds, MPDData{name, uri, ""}) + mpds = append(mpds, MPDData{Name: name, OrigURI: uri}) outData, err := json.MarshalIndent(mpds, "", " ") if err != nil { return err