option to autoplay a related track

This commit is contained in:
Laptop
2025-02-24 23:27:58 +02:00
parent 6daeac4638
commit 3718ef7e66
7 changed files with 124 additions and 64 deletions

View File

@@ -3,15 +3,16 @@
| Name | Key | Default | Possible values | Description | | 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 | | 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 | | 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 | | 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 | | 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 | | 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 | | Default autoplay mode (in playlists) | DefaultAutoplayMode | "normal" | "normal", "random" | Default mode for playlist 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 | | Autoplay next related track | AutoplayNextRelatedTrack | false | true, false | Automatically play a related track next. Requires JS
| Dynamically load comments | DynamicLoadComments | false | true, false | Dynamically load track comments, without leaving the page. Requires JS | | Fetch search suggestions | SearchSuggestions | false | true, false | Load search suggestions on main page when you type. 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 | | 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 ## Player-specific preferences

View File

@@ -130,6 +130,7 @@ func defaultPreferences() {
DefaultPreferences.ParseDescriptions = &True DefaultPreferences.ParseDescriptions = &True
DefaultPreferences.AutoplayNextTrack = &False DefaultPreferences.AutoplayNextTrack = &False
DefaultPreferences.AutoplayNextRelatedTrack = &False
p2 := AutoplayNormal p2 := AutoplayNormal
DefaultPreferences.DefaultAutoplayMode = &p2 DefaultPreferences.DefaultAutoplayMode = &p2
@@ -188,6 +189,12 @@ func loadDefaultPreferences(loaded Preferences) {
DefaultPreferences.AutoplayNextTrack = &False DefaultPreferences.AutoplayNextTrack = &False
} }
if loaded.AutoplayNextRelatedTrack != nil {
DefaultPreferences.AutoplayNextRelatedTrack = loaded.AutoplayNextRelatedTrack
} else {
DefaultPreferences.AutoplayNextRelatedTrack = &False
}
if loaded.DefaultAutoplayMode != nil { if loaded.DefaultAutoplayMode != nil {
DefaultPreferences.DefaultAutoplayMode = loaded.DefaultAutoplayMode DefaultPreferences.DefaultAutoplayMode = loaded.DefaultAutoplayMode
} else { } else {

View File

@@ -64,6 +64,9 @@ type Preferences struct {
// Automatically play next track in playlists // Automatically play next track in playlists
AutoplayNextTrack *bool AutoplayNextTrack *bool
// Automatically play next related track
AutoplayNextRelatedTrack *bool
DefaultAutoplayMode *string // "normal" or "random" DefaultAutoplayMode *string // "normal" or "random"
// Check above for more info // Check above for more info

View File

@@ -36,6 +36,10 @@ func Defaults(dst *cfg.Preferences) {
dst.AutoplayNextTrack = cfg.DefaultPreferences.AutoplayNextTrack dst.AutoplayNextTrack = cfg.DefaultPreferences.AutoplayNextTrack
} }
if dst.AutoplayNextRelatedTrack == nil {
dst.AutoplayNextRelatedTrack = cfg.DefaultPreferences.AutoplayNextRelatedTrack
}
if dst.DefaultAutoplayMode == nil { if dst.DefaultAutoplayMode == nil {
dst.DefaultAutoplayMode = cfg.DefaultPreferences.DefaultAutoplayMode dst.DefaultAutoplayMode = cfg.DefaultPreferences.DefaultAutoplayMode
} }
@@ -79,19 +83,20 @@ func Get(c fiber.Ctx) (cfg.Preferences, error) {
} }
type PrefsForm struct { type PrefsForm struct {
ProxyImages string ProxyImages string
ParseDescriptions string ParseDescriptions string
Player string Player string
ProxyStreams string ProxyStreams string
FullyPreloadTrack string FullyPreloadTrack string
AutoplayNextTrack string AutoplayNextTrack string
DefaultAutoplayMode string AutoplayNextRelatedTrack string
HLSAudio string DefaultAutoplayMode string
RestreamAudio string HLSAudio string
DownloadAudio string RestreamAudio string
ShowAudio string DownloadAudio string
SearchSuggestions string ShowAudio string
DynamicLoadComments string SearchSuggestions string
DynamicLoadComments string
} }
type Export struct { type Export struct {
@@ -131,6 +136,12 @@ func Load(r *fiber.App) {
old.AutoplayNextTrack = &cfg.False old.AutoplayNextTrack = &cfg.False
} }
if p.AutoplayNextRelatedTrack == "on" {
old.AutoplayNextRelatedTrack = &cfg.True
} else {
old.AutoplayNextRelatedTrack = &cfg.False
}
if p.ShowAudio == "on" { if p.ShowAudio == "on" {
old.ShowAudio = &cfg.True old.ShowAudio = &cfg.True
} else { } else {

67
main.go
View File

@@ -52,8 +52,7 @@ func (osfs) Open(name string) (fs.File, error) {
return f, err return f, err
} }
type staticfs struct { type staticfs struct{}
}
func (staticfs) Open(name string) (fs.File, error) { func (staticfs) Open(name string) (fs.File, error) {
misc.Log("staticfs:", name) misc.Log("staticfs:", name)
@@ -150,7 +149,7 @@ func main() {
return err 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) return templates.Base("", templates.MainPage(prefs), templates.MainPageHead()).Render(c.RequestCtx(), c)
} }
@@ -158,7 +157,6 @@ func main() {
app.Get("/index.html", mainPageHandler) app.Get("/index.html", mainPageHandler)
} }
const AssetsCacheControl = "public, max-age=28800" // 8hrs
if cfg.EmbedFiles { if cfg.EmbedFiles {
misc.Log("using embedded files") misc.Log("using embedded files")
ServeFromFS(app, staticfs{}) ServeFromFS(app, staticfs{})
@@ -171,7 +169,7 @@ func main() {
// try to load favicon from default location, // try to load favicon from default location,
// and this path loads the user "favicon" by default // and this path loads the user "favicon" by default
app.Get("favicon.ico", func(c fiber.Ctx) error { 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 { app.Get("robots.txt", func(c fiber.Ctx) error {
@@ -195,7 +193,7 @@ Disallow: /`)
return err 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) return templates.Base("tracks: "+q, templates.SearchTracks(p), nil).Render(c.RequestCtx(), c)
case "users": case "users":
@@ -205,7 +203,7 @@ Disallow: /`)
return err 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) return templates.Base("users: "+q, templates.SearchUsers(p), nil).Render(c.RequestCtx(), c)
case "playlists": case "playlists":
@@ -215,7 +213,7 @@ Disallow: /`)
return err 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) 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) return templates.TrackEmbed(prefs, track, stream, displayErr).Render(c.RequestCtx(), c)
}) })
@@ -332,7 +330,7 @@ Disallow: /`)
return err 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) return templates.Base("Recent tracks tagged "+tag, templates.RecentTracks(tag, p), nil).Render(c.RequestCtx(), c)
}) })
@@ -354,7 +352,7 @@ Disallow: /`)
return err 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) return templates.Base("Popular tracks tagged "+tag, templates.PopularTracks(tag, p), nil).Render(c.RequestCtx(), c)
}) })
@@ -377,7 +375,7 @@ Disallow: /`)
return err 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) return templates.Base("Playlists tagged "+tag, templates.TaggedPlaylists(tag, p), nil).Render(c.RequestCtx(), c)
}) })
@@ -393,7 +391,7 @@ Disallow: /`)
return err 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) return templates.Base("Featured Tracks", templates.FeaturedTracks(tracks), nil).Render(c.RequestCtx(), c)
}) })
@@ -409,7 +407,7 @@ Disallow: /`)
return err 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) 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 { app.Get("/_/info", func(c fiber.Ctx) error {
c.Set("Content-Type", "application/json") c.Response().Header.SetContentType("application/json")
return c.Send(inf) return c.Send(inf)
}) })
} }
@@ -500,7 +498,7 @@ Disallow: /`)
return err 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) return templates.Base(user.Username, templates.UserPlaylists(prefs, user, pl), templates.UserHeader(user)).Render(c.RequestCtx(), c)
}) })
@@ -528,7 +526,7 @@ Disallow: /`)
return err 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) return templates.Base(user.Username, templates.UserAlbums(prefs, user, pl), templates.UserHeader(user)).Render(c.RequestCtx(), c)
}) })
@@ -556,7 +554,7 @@ Disallow: /`)
return err 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) return templates.Base(user.Username, templates.UserReposts(prefs, user, p), templates.UserHeader(user)).Render(c.RequestCtx(), c)
}) })
@@ -584,7 +582,7 @@ Disallow: /`)
return err 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) return templates.Base(user.Username, templates.UserLikes(prefs, user, p), templates.UserHeader(user)).Render(c.RequestCtx(), c)
}) })
@@ -612,7 +610,7 @@ Disallow: /`)
return err 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) return templates.Base(user.Username, templates.UserTopTracks(prefs, user, p), templates.UserHeader(user)).Render(c.RequestCtx(), c)
}) })
@@ -640,7 +638,7 @@ Disallow: /`)
return err 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) return templates.Base(user.Username, templates.UserFollowers(prefs, user, p), templates.UserHeader(user)).Render(c.RequestCtx(), c)
}) })
@@ -668,7 +666,7 @@ Disallow: /`)
return err 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) 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] var comments *sc.Paginated[*sc.Comment]
if q := c.Query("pagination"); q != "" { if q := c.Query("pagination"); q != "" {
comments, err = track.GetComments(cid, prefs, q) comments, err = track.GetComments(cid, prefs, q)
@@ -777,7 +786,7 @@ Disallow: /`)
downloadAudio = &audio 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) 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 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) 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, ",") 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) 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 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) return templates.Base(user.Username, templates.UserRelated(prefs, user, r), templates.UserHeader(user)).Render(c.RequestCtx(), c)
}) })
@@ -933,7 +942,7 @@ Disallow: /`)
return err 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) 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 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) 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 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) return templates.Base(track.Title+" by "+track.Author.Username, templates.TrackInAlbums(track, p), templates.TrackHeader(prefs, track, false)).Render(c.RequestCtx(), c)
}) })

View File

@@ -69,13 +69,18 @@ templ Preferences(prefs cfg.Preferences) {
</label> </label>
if *prefs.AutoplayNextTrack { if *prefs.AutoplayNextTrack {
<label> <label>
Default autoplay mode: Default autoplay mode (in playlists):
@sel("DefaultAutoplayMode", []option{ @sel("DefaultAutoplayMode", []option{
{"normal", "Normal (play songs in order)", false}, {"normal", "Normal (play songs in order)", false},
{"random", "Random (play random song)", false}, {"random", "Random (play random song)", false},
}, *prefs.DefaultAutoplayMode) }, *prefs.DefaultAutoplayMode)
</label> </label>
} }
<label>
Autoplay next related track:
@checkbox("AutoplayNextRelatedTrack", *prefs.AutoplayNextRelatedTrack)
(requires JS; you need to allow autoplay from this domain!!)
</label>
<label> <label>
Fetch search suggestions: Fetch search suggestions:
@checkbox("SearchSuggestions", *prefs.SearchSuggestions) @checkbox("SearchSuggestions", *prefs.SearchSuggestions)

View File

@@ -52,17 +52,33 @@ templ TrackHeader(prefs cfg.Preferences, t sc.Track, needPlayer bool) {
} }
} }
func next(t *sc.Track, p *sc.Playlist, autoplay bool, mode string, volume string) string { func next(c *sc.Track, t *sc.Track, p *sc.Playlist, autoplay bool, mode string, volume string) string {
r := t.Href() + "?playlist=" + p.Href()[1:] r := t.Href()
if autoplay {
r += "&autoplay=true" if p != nil {
r += "?playlist=" + p.Href()[1:]
} }
if autoplay {
if p == nil {
r += "?"
} else {
r += "&"
}
r += "autoplay=true"
}
if mode != "" { if mode != "" {
r += "&mode=" + mode r += "&mode=" + mode
} }
if volume != "" { if volume != "" {
r += "&volume=" + volume r += "&volume=" + volume
} }
if p == nil {
r += "&prev=" + string(c.ID)
}
return r return r
} }
@@ -75,7 +91,7 @@ templ TrackPlayer(prefs cfg.Preferences, track sc.Track, stream string, displayE
if cfg.Restream && *prefs.Player == cfg.RestreamPlayer { if cfg.Restream && *prefs.Player == cfg.RestreamPlayer {
{{ audioPref = *prefs.RestreamAudio }} {{ audioPref = *prefs.RestreamAudio }}
if nextTrack != nil { if nextTrack != nil {
<audio id="track" src={ "/_/restream" + track.Href() } controls autoplay?={ autoplay } data-next={ next(nextTrack, playlist, true, mode, "") } volume={ volume }></audio> <audio id="track" src={ "/_/restream" + track.Href() } controls autoplay?={ autoplay } data-next={ next(&track, nextTrack, playlist, true, mode, "") } volume={ volume }></audio>
<script async src="/_/static/restream.js"></script> <script async src="/_/static/restream.js"></script>
} else { } else {
<audio src={ "/_/restream" + track.Href() } controls autoplay?={ autoplay }></audio> <audio src={ "/_/restream" + track.Href() } controls autoplay?={ autoplay }></audio>
@@ -83,7 +99,7 @@ templ TrackPlayer(prefs cfg.Preferences, track sc.Track, stream string, displayE
} else if stream != "" { } else if stream != "" {
{{ audioPref = *prefs.HLSAudio }} {{ audioPref = *prefs.HLSAudio }}
if nextTrack != nil { if nextTrack != nil {
<audio id="track" src={ stream } controls autoplay?={ autoplay } data-next={ next(nextTrack, playlist, true, mode, "") } volume={ volume }></audio> <audio id="track" src={ stream } controls autoplay?={ autoplay } data-next={ next(&track, nextTrack, playlist, true, mode, "") } volume={ volume }></audio>
} else { } else {
<audio id="track" src={ stream } controls autoplay?={ autoplay }></audio> <audio id="track" src={ stream } controls autoplay?={ autoplay }></audio>
} }
@@ -159,20 +175,28 @@ templ Track(prefs cfg.Preferences, t sc.Track, stream string, displayErr string,
if t.Genre != "" { if t.Genre != "" {
<a href={ templ.SafeURL("/tags/" + t.Genre) }><p class="tag">{ t.Genre }</p></a> <a href={ templ.SafeURL("/tags/" + t.Genre) }><p class="tag">{ t.Genre }</p></a>
} }
if playlist != nil { if nextTrack != nil {
<details open style="margin-bottom: 1rem;"> <details open style="margin-bottom: 1rem;">
<summary>Playback info</summary> <summary>Playback info</summary>
<h2>In playlist:</h2>
@PlaylistItem(playlist, true) if playlist != nil {
<h2>In playlist:</h2>
@PlaylistItem(playlist, true)
}
<h2>Next track:</h2> <h2>Next track:</h2>
@TrackItem(nextTrack, true, next(nextTrack, playlist, true, mode, volume)) @TrackItem(nextTrack, true, next(&t, nextTrack, playlist, true, mode, volume))
<div style="display: flex; gap: 1rem"> <div style="display: flex; gap: 1rem">
if playlist != nil {
<a href={ templ.SafeURL(t.Href()) } class="btn">Stop playlist playback</a> <a href={ templ.SafeURL(t.Href()) } class="btn">Stop playlist playback</a>
if mode != cfg.AutoplayRandom { if mode != cfg.AutoplayRandom {
<a href={ templ.SafeURL(next(&t, playlist, false, cfg.AutoplayRandom, volume)) } class="btn">Switch to random mode</a> <a href={ templ.SafeURL(next(nil, &t, playlist, false, cfg.AutoplayRandom, volume)) } class="btn">Switch to random mode</a>
} else { } else {
<a href={ templ.SafeURL(next(&t, playlist, false, cfg.AutoplayNormal, volume)) } class="btn">Switch to normal mode</a> <a href={ templ.SafeURL(next(nil, &t, playlist, false, cfg.AutoplayNormal, volume)) } class="btn">Switch to normal mode</a>
} }
} else {
<a href={ templ.SafeURL(t.Href() + "?playRelated=false") } class="btn">Stop playback</a>
}
</div> </div>
</details> </details>
} }