diff --git a/docs/PREFERENCES.md b/docs/PREFERENCES.md index 7908861..5792c34 100644 --- a/docs/PREFERENCES.md +++ b/docs/PREFERENCES.md @@ -3,15 +3,16 @@ | Name | Key | Default | Possible values | Description | | :--------------------------------- | --------------------- | ------------------------------------------------------------------------ | ------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Parse descriptions | ParseDescriptions | true | true, false | Turn @mentions, external links (https://example.org) and emails (hello@example.org) inside descriptions into clickable links | -| Show current audio | ShowAudio | false | true, false | Show what [audio preset](AUDIO_PRESETS.md) is being streamed below the audio player | -| Proxy images | ProxyImages | same as ProxyImages in backend config | true, false | Proxy images through the backend. ProxyImages must be enabled on the backend | -| Download audio | DownloadAudio | "mpeg" | "mpeg", "opus", "aac", "best" | What [audio preset](AUDIO_PRESETS.md) should be loaded when downloading audio with metadata. Restream must be enabled on the backend | -| Autoplay next track in playlists | AutoplayNextTrack | false | true, false | Automatically start playlist playback when you open a track from the playlist. Requires JS | -| Default autoplay mode | DefaultAutoplayMode | "normal" | "normal", "random" | Default mode for autoplay. Normal - play songs in order. Random - play random song next | -| Fetch search suggestions | SearchSuggestions | false | true, false | Load search suggestions on main page when you type. Requires JS | -| Dynamically load comments | DynamicLoadComments | false | true, false | Dynamically load track comments, without leaving the page. Requires JS | -| Player | Player | "restream" if Restream is enabled in backend config, otherwise - "hls" | "restream", "hls", "none" | Method used to play the track in the frontend. HLS - requires JavaScript, loads the track in pieces. Restream - works without JavaScript, loads entire track through the backend right away. None - don't play the track | +| Parse descriptions | ParseDescriptions | true | true, false | Turn @mentions, external links (https://example.org) and emails (hello@example.org) inside descriptions into clickable links | +| Show current audio | ShowAudio | false | true, false | Show what [audio preset](AUDIO_PRESETS.md) is being streamed below the audio player | +| Proxy images | ProxyImages | same as ProxyImages in backend config | true, false | Proxy images through the backend. ProxyImages must be enabled on the backend | +| Download audio | DownloadAudio | "mpeg" | "mpeg", "opus", "aac", "best" | What [audio preset](AUDIO_PRESETS.md) should be loaded when downloading audio with metadata. Restream must be enabled on the backend | +| Autoplay next track in playlists | AutoplayNextTrack | false | true, false | Automatically start playlist playback when you open a track from the playlist. Requires JS | +| Default autoplay mode (in playlists) | DefaultAutoplayMode | "normal" | "normal", "random" | Default mode for playlist autoplay. Normal - play songs in order. Random - play random song next | +| Autoplay next related track | AutoplayNextRelatedTrack | false | true, false | Automatically play a related track next. Requires JS +| Fetch search suggestions | SearchSuggestions | false | true, false | Load search suggestions on main page when you type. Requires JS | +| Dynamically load comments | DynamicLoadComments | false | true, false | Dynamically load track comments, without leaving the page. Requires JS | +| Player | Player | "restream" if Restream is enabled in backend config, otherwise - "hls" | "restream", "hls", "none" | Method used to play the track in the frontend. HLS - requires JavaScript, loads the track in pieces. Restream - works without JavaScript, loads entire track through the backend right away. None - don't play the track | ## Player-specific preferences diff --git a/lib/cfg/init.go b/lib/cfg/init.go index dfc3530..6eb7301 100644 --- a/lib/cfg/init.go +++ b/lib/cfg/init.go @@ -130,6 +130,7 @@ func defaultPreferences() { DefaultPreferences.ParseDescriptions = &True DefaultPreferences.AutoplayNextTrack = &False + DefaultPreferences.AutoplayNextRelatedTrack = &False p2 := AutoplayNormal DefaultPreferences.DefaultAutoplayMode = &p2 @@ -188,6 +189,12 @@ func loadDefaultPreferences(loaded Preferences) { DefaultPreferences.AutoplayNextTrack = &False } + if loaded.AutoplayNextRelatedTrack != nil { + DefaultPreferences.AutoplayNextRelatedTrack = loaded.AutoplayNextRelatedTrack + } else { + DefaultPreferences.AutoplayNextRelatedTrack = &False + } + if loaded.DefaultAutoplayMode != nil { DefaultPreferences.DefaultAutoplayMode = loaded.DefaultAutoplayMode } else { diff --git a/lib/cfg/misc.go b/lib/cfg/misc.go index 4c778a4..e7e1590 100644 --- a/lib/cfg/misc.go +++ b/lib/cfg/misc.go @@ -64,6 +64,9 @@ type Preferences struct { // Automatically play next track in playlists AutoplayNextTrack *bool + // Automatically play next related track + AutoplayNextRelatedTrack *bool + DefaultAutoplayMode *string // "normal" or "random" // Check above for more info diff --git a/lib/preferences/init.go b/lib/preferences/init.go index 46641f1..cf9909e 100644 --- a/lib/preferences/init.go +++ b/lib/preferences/init.go @@ -36,6 +36,10 @@ func Defaults(dst *cfg.Preferences) { dst.AutoplayNextTrack = cfg.DefaultPreferences.AutoplayNextTrack } + if dst.AutoplayNextRelatedTrack == nil { + dst.AutoplayNextRelatedTrack = cfg.DefaultPreferences.AutoplayNextRelatedTrack + } + if dst.DefaultAutoplayMode == nil { dst.DefaultAutoplayMode = cfg.DefaultPreferences.DefaultAutoplayMode } @@ -79,19 +83,20 @@ func Get(c fiber.Ctx) (cfg.Preferences, error) { } type PrefsForm struct { - ProxyImages string - ParseDescriptions string - Player string - ProxyStreams string - FullyPreloadTrack string - AutoplayNextTrack string - DefaultAutoplayMode string - HLSAudio string - RestreamAudio string - DownloadAudio string - ShowAudio string - SearchSuggestions string - DynamicLoadComments string + ProxyImages string + ParseDescriptions string + Player string + ProxyStreams string + FullyPreloadTrack string + AutoplayNextTrack string + AutoplayNextRelatedTrack string + DefaultAutoplayMode string + HLSAudio string + RestreamAudio string + DownloadAudio string + ShowAudio string + SearchSuggestions string + DynamicLoadComments string } type Export struct { @@ -131,6 +136,12 @@ func Load(r *fiber.App) { old.AutoplayNextTrack = &cfg.False } + if p.AutoplayNextRelatedTrack == "on" { + old.AutoplayNextRelatedTrack = &cfg.True + } else { + old.AutoplayNextRelatedTrack = &cfg.False + } + if p.ShowAudio == "on" { old.ShowAudio = &cfg.True } else { diff --git a/main.go b/main.go index 47af6c4..5c2014e 100644 --- a/main.go +++ b/main.go @@ -52,8 +52,7 @@ func (osfs) Open(name string) (fs.File, error) { return f, err } -type staticfs struct { -} +type staticfs struct{} func (staticfs) Open(name string) (fs.File, error) { misc.Log("staticfs:", name) @@ -150,7 +149,7 @@ func main() { return err } - c.Set("Content-Type", "text/html") + c.Response().Header.SetContentType("text/html") return templates.Base("", templates.MainPage(prefs), templates.MainPageHead()).Render(c.RequestCtx(), c) } @@ -158,7 +157,6 @@ func main() { app.Get("/index.html", mainPageHandler) } - const AssetsCacheControl = "public, max-age=28800" // 8hrs if cfg.EmbedFiles { misc.Log("using embedded files") ServeFromFS(app, staticfs{}) @@ -171,7 +169,7 @@ func main() { // try to load favicon from default location, // and this path loads the user "favicon" by default app.Get("favicon.ico", func(c fiber.Ctx) error { - return c.Redirect().To("/_/static/favicon.ico") + return c.Redirect().Status(fiber.StatusPermanentRedirect).To("/_/static/favicon.ico") }) app.Get("robots.txt", func(c fiber.Ctx) error { @@ -195,7 +193,7 @@ Disallow: /`) return err } - c.Set("Content-Type", "text/html") + c.Response().Header.SetContentType("text/html") return templates.Base("tracks: "+q, templates.SearchTracks(p), nil).Render(c.RequestCtx(), c) case "users": @@ -205,7 +203,7 @@ Disallow: /`) return err } - c.Set("Content-Type", "text/html") + c.Response().Header.SetContentType("text/html") return templates.Base("users: "+q, templates.SearchUsers(p), nil).Render(c.RequestCtx(), c) case "playlists": @@ -215,7 +213,7 @@ Disallow: /`) return err } - c.Set("Content-Type", "text/html") + c.Response().Header.SetContentType("text/html") return templates.Base("playlists: "+q, templates.SearchPlaylists(p), nil).Render(c.RequestCtx(), c) } @@ -310,7 +308,7 @@ Disallow: /`) } } - c.Set("Content-Type", "text/html") + c.Response().Header.SetContentType("text/html") return templates.TrackEmbed(prefs, track, stream, displayErr).Render(c.RequestCtx(), c) }) @@ -332,7 +330,7 @@ Disallow: /`) return err } - c.Set("Content-Type", "text/html") + c.Response().Header.SetContentType("text/html") return templates.Base("Recent tracks tagged "+tag, templates.RecentTracks(tag, p), nil).Render(c.RequestCtx(), c) }) @@ -354,7 +352,7 @@ Disallow: /`) return err } - c.Set("Content-Type", "text/html") + c.Response().Header.SetContentType("text/html") return templates.Base("Popular tracks tagged "+tag, templates.PopularTracks(tag, p), nil).Render(c.RequestCtx(), c) }) @@ -377,7 +375,7 @@ Disallow: /`) return err } - c.Set("Content-Type", "text/html") + c.Response().Header.SetContentType("text/html") return templates.Base("Playlists tagged "+tag, templates.TaggedPlaylists(tag, p), nil).Render(c.RequestCtx(), c) }) @@ -393,7 +391,7 @@ Disallow: /`) return err } - c.Set("Content-Type", "text/html") + c.Response().Header.SetContentType("text/html") return templates.Base("Featured Tracks", templates.FeaturedTracks(tracks), nil).Render(c.RequestCtx(), c) }) @@ -409,7 +407,7 @@ Disallow: /`) return err } - c.Set("Content-Type", "text/html") + c.Response().Header.SetContentType("text/html") return templates.Base("Discover", templates.Discover(selections), nil).Render(c.RequestCtx(), c) }) @@ -446,7 +444,7 @@ Disallow: /`) } app.Get("/_/info", func(c fiber.Ctx) error { - c.Set("Content-Type", "application/json") + c.Response().Header.SetContentType("application/json") return c.Send(inf) }) } @@ -500,7 +498,7 @@ Disallow: /`) return err } - c.Set("Content-Type", "text/html") + c.Response().Header.SetContentType("text/html") return templates.Base(user.Username, templates.UserPlaylists(prefs, user, pl), templates.UserHeader(user)).Render(c.RequestCtx(), c) }) @@ -528,7 +526,7 @@ Disallow: /`) return err } - c.Set("Content-Type", "text/html") + c.Response().Header.SetContentType("text/html") return templates.Base(user.Username, templates.UserAlbums(prefs, user, pl), templates.UserHeader(user)).Render(c.RequestCtx(), c) }) @@ -556,7 +554,7 @@ Disallow: /`) return err } - c.Set("Content-Type", "text/html") + c.Response().Header.SetContentType("text/html") return templates.Base(user.Username, templates.UserReposts(prefs, user, p), templates.UserHeader(user)).Render(c.RequestCtx(), c) }) @@ -584,7 +582,7 @@ Disallow: /`) return err } - c.Set("Content-Type", "text/html") + c.Response().Header.SetContentType("text/html") return templates.Base(user.Username, templates.UserLikes(prefs, user, p), templates.UserHeader(user)).Render(c.RequestCtx(), c) }) @@ -612,7 +610,7 @@ Disallow: /`) return err } - c.Set("Content-Type", "text/html") + c.Response().Header.SetContentType("text/html") return templates.Base(user.Username, templates.UserTopTracks(prefs, user, p), templates.UserHeader(user)).Render(c.RequestCtx(), c) }) @@ -640,7 +638,7 @@ Disallow: /`) return err } - c.Set("Content-Type", "text/html") + c.Response().Header.SetContentType("text/html") return templates.Base(user.Username, templates.UserFollowers(prefs, user, p), templates.UserHeader(user)).Render(c.RequestCtx(), c) }) @@ -668,7 +666,7 @@ Disallow: /`) return err } - c.Set("Content-Type", "text/html") + c.Response().Header.SetContentType("text/html") return templates.Base(user.Username, templates.UserFollowing(prefs, user, p), templates.UserHeader(user)).Render(c.RequestCtx(), c) }) @@ -762,6 +760,17 @@ Disallow: /`) } } + if *prefs.AutoplayNextRelatedTrack && nextTrack == nil && string(c.RequestCtx().QueryArgs().Peek("playRelated")) != "false" { + rel, err := track.GetRelated(cid, prefs, "?limit=4") + if err == nil && len(rel.Collection) != 0 { + prev := c.RequestCtx().QueryArgs().Peek("prev") + nextTrack = &track + for i := len(rel.Collection) - 1; i >= 0 && (string(nextTrack.ID) == string(track.ID) || string(nextTrack.ID) == string(prev)); i-- { + nextTrack = rel.Collection[i] + } + } + } + var comments *sc.Paginated[*sc.Comment] if q := c.Query("pagination"); q != "" { comments, err = track.GetComments(cid, prefs, q) @@ -777,7 +786,7 @@ Disallow: /`) downloadAudio = &audio } - c.Set("Content-Type", "text/html") + c.Response().Header.SetContentType("text/html") return templates.Base(track.Title+" by "+track.Author.Username, templates.Track(prefs, track, stream, displayErr, string(c.RequestCtx().QueryArgs().Peek("autoplay")) == "true", playlist, nextTrack, c.Query("volume"), mode, audio, downloadAudio, comments), templates.TrackHeader(prefs, track, true)).Render(c.RequestCtx(), c) }) @@ -836,7 +845,7 @@ Disallow: /`) return err } - c.Set("Content-Type", "text/html") + c.Response().Header.SetContentType("text/html") return templates.Base(usr.Username, templates.User(prefs, usr, p), templates.UserHeader(usr)).Render(c.RequestCtx(), c) }) @@ -876,7 +885,7 @@ Disallow: /`) playlist.MissingTracks = strings.Join(next, ",") } - c.Set("Content-Type", "text/html") + c.Response().Header.SetContentType("text/html") return templates.Base(playlist.Title+" by "+playlist.Author.Username, templates.Playlist(prefs, playlist), templates.PlaylistHeader(playlist)).Render(c.RequestCtx(), c) }) @@ -904,7 +913,7 @@ Disallow: /`) return err } - c.Set("Content-Type", "text/html") + c.Response().Header.SetContentType("text/html") return templates.Base(user.Username, templates.UserRelated(prefs, user, r), templates.UserHeader(user)).Render(c.RequestCtx(), c) }) @@ -933,7 +942,7 @@ Disallow: /`) return err } - c.Set("Content-Type", "text/html") + c.Response().Header.SetContentType("text/html") return templates.Base(track.Title+" by "+track.Author.Username, templates.RelatedTracks(track, r), templates.TrackHeader(prefs, track, false)).Render(c.RequestCtx(), c) }) @@ -961,7 +970,7 @@ Disallow: /`) return err } - c.Set("Content-Type", "text/html") + c.Response().Header.SetContentType("text/html") return templates.Base(track.Title+" by "+track.Author.Username, templates.TrackInPlaylists(track, p), templates.TrackHeader(prefs, track, false)).Render(c.RequestCtx(), c) }) @@ -989,7 +998,7 @@ Disallow: /`) return err } - c.Set("Content-Type", "text/html") + c.Response().Header.SetContentType("text/html") return templates.Base(track.Title+" by "+track.Author.Username, templates.TrackInAlbums(track, p), templates.TrackHeader(prefs, track, false)).Render(c.RequestCtx(), c) }) diff --git a/templates/preferences.templ b/templates/preferences.templ index 197edfe..75f323e 100644 --- a/templates/preferences.templ +++ b/templates/preferences.templ @@ -69,13 +69,18 @@ templ Preferences(prefs cfg.Preferences) { if *prefs.AutoplayNextTrack { } +