mirror of
https://git.maid.zone/stuff/soundcloak.git
synced 2025-12-10 13:49:39 +05:00
Autoplay tracks in playlists; increase caching time; some fixes
This commit is contained in:
47
README.md
47
README.md
@@ -37,6 +37,7 @@ Available features:
|
||||
- Player-specific settings: They will only show up if you have selected HLS player currently.
|
||||
- - Proxy streams: Retrieve song pieces through the instance, instead of going to soundcloud's servers for them
|
||||
- - Fully preload track: Fully loads the track when you load the page instead of buffering a small part of it
|
||||
- - Autoplay next track in playlists: self-explanatory
|
||||
|
||||
# Contributing
|
||||
|
||||
@@ -188,29 +189,29 @@ Some notes:
|
||||
- When specifying time, specify it in seconds.
|
||||
|
||||
|
||||
| JSON key | Environment variable | Default value | Description |
|
||||
| :------------------------ | ---------------------------- | ----------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| None | SOUNDCLOAK_CONFIG | soundcloak.json | File to load soundcloak config from. If set to `FROM_ENV`, soundcloak loads the config from environment variables. |
|
||||
| GetWebProfiles | GET_WEB_PROFILES | true | Retrieve links users set in their profile (social media, website, etc) |
|
||||
| DefaultPreferences | DEFAULT_PREFERENCES | {"Player": "hls", "ProxyStreams": false, "FullyPreloadTrack": false, "ProxyImages": false, "ParseDescriptions": true} | see /_/preferences page, default values adapt to your config (Player: "restream" if Restream, else "hls", ProxyStreams and ProxyImages will be same as respective config values) |
|
||||
| ProxyImages | PROXY_IMAGES | false | Enables proxying of images (user avatars, track covers etc) |
|
||||
| ImageCacheControl | IMAGE_CACHE_CONTROL | max-age=600, public, immutable | [Cache-Control](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control) header value for proxied images. Cached for 10 minutes by default. |
|
||||
| ProxyStreams | PROXY_STREAMS | false | Enables proxying of song parts and hls playlist files |
|
||||
| Restream | RESTREAM | false | Enables Restream Player in settings and the /_/restream/:author/:track endpoint. This player can be used without JavaScript. Restream also enables the button for downloading songs. |
|
||||
| RestreamCacheControl | RESTREAM_CACHE_CONTROL | max-age=3600, public, immutable | [Cache-Control](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control) header value for restreamed songs. Cached for 1 hour by default. |
|
||||
| ClientIDTTL | CLIENT_ID_TTL | 30 minutes | Time until ClientID cache expires. ClientID is used for authenticating with SoundCloud API |
|
||||
| UserTTL | USER_TTL | 10 minutes | Time until User profile cache expires |
|
||||
| UserCacheCleanDelay | USER_CACHE_CLEAN_DELAY | 2.5 minutes | Time between each cleanup of the cache (to remove expired users) |
|
||||
| TrackTTL | TRACK_TTL | 10 minutes | Time until Track data cache expires |
|
||||
| TrackCacheCleanDelay | TRACK_CACHE_CLEAN_DELAY | 2.5 minutes | Time between each cleanup of the cache (to remove expired tracks) |
|
||||
| PlaylistTTL | PLAYLIST_TTL | 10 minutes | Time until Playlist data cache expires |
|
||||
| PlaylistCacheCleanDelay | PLAYLIST_CACHE_CLEAN_DELAY | 2.5 minutes | Time between each cleanup of the cache (to remove expired playlists) |
|
||||
| UserAgent | USER_AGENT | Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.3 | User-Agent header used for requests to SoundCloud |
|
||||
| DNSCacheTTL | DNS_CACHE_TTL | 10 minutes | Time until DNS cache expires |
|
||||
| Addr | ADDR | :4664 | Address and port for soundcloak to listen on |
|
||||
| Prefork | PREFORK | false | Run multiple instances of soundcloak locally to be able to handle more requests. Each one will be a separate process, so they will have separate cache. |
|
||||
| TrustedProxyCheck | TRUSTED_PROXY_CHECK | true | Use X-Forwarded-* headers if IP is in TrustedProxies list. When disabled, those headers will blindly be used. |
|
||||
| TrustedProxies | TRUSTED_PROXIES | [] | List of IPs or IP ranges of trusted proxies |
|
||||
| JSON key | Environment variable | Default value | Description |
|
||||
| :------------------------ | ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| None | SOUNDCLOAK_CONFIG | soundcloak.json | File to load soundcloak config from. If set to`FROM_ENV`, soundcloak loads the config from environment variables. |
|
||||
| GetWebProfiles | GET_WEB_PROFILES | true | Retrieve links users set in their profile (social media, website, etc) |
|
||||
| DefaultPreferences | DEFAULT_PREFERENCES | {"Player": "hls", "ProxyStreams": false, "FullyPreloadTrack": false, "ProxyImages": false, "ParseDescriptions": true, "AutoplayNextTrack": false} | see /_/preferences page, default values adapt to your config (Player: "restream" if Restream, else "hls", ProxyStreams and ProxyImages will be same as respective config values) |
|
||||
| ProxyImages | PROXY_IMAGES | false | Enables proxying of images (user avatars, track covers etc) |
|
||||
| ImageCacheControl | IMAGE_CACHE_CONTROL | max-age=600, public, immutable | [Cache-Control](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control) header value for proxied images. Cached for 10 minutes by default. |
|
||||
| ProxyStreams | PROXY_STREAMS | false | Enables proxying of song parts and hls playlist files |
|
||||
| Restream | RESTREAM | false | Enables Restream Player in settings and the /_/restream/:author/:track endpoint. This player can be used without JavaScript. Restream also enables the button for downloading songs. |
|
||||
| RestreamCacheControl | RESTREAM_CACHE_CONTROL | max-age=3600, public, immutable | [Cache-Control](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control) header value for restreamed songs. Cached for 1 hour by default. |
|
||||
| ClientIDTTL | CLIENT_ID_TTL | 4 hours | Time until ClientID cache expires. ClientID is used for authenticating with SoundCloud API |
|
||||
| UserTTL | USER_TTL | 20 minutes | Time until User profile cache expires |
|
||||
| UserCacheCleanDelay | USER_CACHE_CLEAN_DELAY | 5 minutes | Time between each cleanup of the cache (to remove expired users) |
|
||||
| TrackTTL | TRACK_TTL | 20 minutes | Time until Track data cache expires |
|
||||
| TrackCacheCleanDelay | TRACK_CACHE_CLEAN_DELAY | 5 minutes | Time between each cleanup of the cache (to remove expired tracks) |
|
||||
| PlaylistTTL | PLAYLIST_TTL | 20 minutes | Time until Playlist data cache expires |
|
||||
| PlaylistCacheCleanDelay | PLAYLIST_CACHE_CLEAN_DELAY | 5 minutes | Time between each cleanup of the cache (to remove expired playlists) |
|
||||
| UserAgent | USER_AGENT | Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.3 | User-Agent header used for requests to SoundCloud |
|
||||
| DNSCacheTTL | DNS_CACHE_TTL | 60 minutes | Time until DNS cache expires |
|
||||
| Addr | ADDR | :4664 | Address and port for soundcloak to listen on |
|
||||
| Prefork | PREFORK | false | Run multiple instances of soundcloak locally to be able to handle more requests. Each one will be a separate process, so they will have separate cache. |
|
||||
| TrustedProxyCheck | TRUSTED_PROXY_CHECK | true | Use X-Forwarded-* headers if IP is in TrustedProxies list. When disabled, those headers will blindly be used. |
|
||||
| TrustedProxies | TRUSTED_PROXIES | [] | List of IPs or IP ranges of trusted proxies |
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
@@ -3,6 +3,18 @@ if (Hls.isSupported()) {
|
||||
var hls = new Hls();
|
||||
hls.loadSource(audio.src);
|
||||
hls.attachMedia(audio);
|
||||
|
||||
var volume = audio.getAttribute('volume');
|
||||
if (volume) {
|
||||
audio.volume = parseFloat(volume);
|
||||
}
|
||||
} else if (!audio.canPlayType('application/vnd.apple.mpegurl')) {
|
||||
alert('HLS is not supported! Audio playback will not work.');
|
||||
}
|
||||
|
||||
var next = audio.getAttribute('data-next');
|
||||
if (next) {
|
||||
audio.addEventListener('ended', function() {
|
||||
location = next + '&volume=' + audio.volume;
|
||||
});
|
||||
}
|
||||
@@ -3,6 +3,18 @@ if (Hls.isSupported()) {
|
||||
var hls = new Hls({ maxBufferLength: Infinity });
|
||||
hls.loadSource(audio.src);
|
||||
hls.attachMedia(audio);
|
||||
|
||||
var volume = audio.getAttribute('volume');
|
||||
if (volume) {
|
||||
audio.volume = parseFloat(volume);
|
||||
}
|
||||
} else if (!audio.canPlayType('application/vnd.apple.mpegurl')) {
|
||||
alert('HLS is not supported! Audio playback will not work.');
|
||||
}
|
||||
|
||||
var next = audio.getAttribute('data-next');
|
||||
if (next) {
|
||||
audio.addEventListener('ended', function() {
|
||||
location = next + '&volume=' + audio.volume;
|
||||
});
|
||||
}
|
||||
@@ -38,6 +38,9 @@ type Preferences struct {
|
||||
|
||||
// Highlight @username, https://example.com and email@example.com in text as clickable links
|
||||
ParseDescriptions *bool
|
||||
|
||||
// Automatically play next track in playlists
|
||||
AutoplayNextTrack *bool
|
||||
}
|
||||
|
||||
// // config // //
|
||||
@@ -67,22 +70,23 @@ var InstanceInfo = true
|
||||
|
||||
// time-to-live for clientid cache
|
||||
// larger number will improve performance (no need to recheck everytime) but might make soundcloak briefly unusable for a larger amount of time if the client id is invalidated
|
||||
var ClientIDTTL = 30 * time.Minute
|
||||
// I went with 4 hours, since those clientids still remain active for quite some time, even after soundcloud updates it
|
||||
var ClientIDTTL = 4 * time.Hour
|
||||
|
||||
// time-to-live for user profile cache
|
||||
var UserTTL = 10 * time.Minute
|
||||
var UserTTL = 20 * time.Minute
|
||||
|
||||
// delay between cleanup of user cache
|
||||
var UserCacheCleanDelay = UserTTL / 4
|
||||
|
||||
// time-to-live for track cache
|
||||
var TrackTTL = 10 * time.Minute
|
||||
var TrackTTL = 20 * time.Minute
|
||||
|
||||
// delay between cleanup of track cache
|
||||
var TrackCacheCleanDelay = TrackTTL / 4
|
||||
|
||||
// time-to-live for playlist cache
|
||||
var PlaylistTTL = 10 * time.Minute
|
||||
var PlaylistTTL = 20 * time.Minute
|
||||
|
||||
// delay between cleanup of playlist cache
|
||||
var PlaylistCacheCleanDelay = PlaylistTTL / 4
|
||||
@@ -91,7 +95,7 @@ var PlaylistCacheCleanDelay = PlaylistTTL / 4
|
||||
var UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.3"
|
||||
|
||||
// time-to-live for dns cache
|
||||
var DNSCacheTTL = 10 * time.Minute
|
||||
var DNSCacheTTL = 60 * time.Minute
|
||||
|
||||
// // // some webserver configuration, put here to make it easier to configure what you need // // //
|
||||
// more info can be found here: https://docs.gofiber.io/api/fiber#config
|
||||
@@ -118,6 +122,7 @@ var TrustedProxies = []string{}
|
||||
// FullyPreloadTrack: false
|
||||
// ProxyImages: same as ProxyImages in your config (false by default)
|
||||
// ParseDescriptions: true
|
||||
// AutoplayNextTrack: false
|
||||
func defaultPreferences() {
|
||||
var p string
|
||||
if Restream {
|
||||
@@ -136,6 +141,7 @@ func defaultPreferences() {
|
||||
DefaultPreferences.ProxyImages = &ProxyImages
|
||||
|
||||
DefaultPreferences.ParseDescriptions = &t
|
||||
DefaultPreferences.AutoplayNextTrack = &f
|
||||
}
|
||||
|
||||
func loadDefaultPreferences(loaded Preferences) {
|
||||
@@ -176,6 +182,12 @@ func loadDefaultPreferences(loaded Preferences) {
|
||||
} else {
|
||||
DefaultPreferences.ParseDescriptions = &t
|
||||
}
|
||||
|
||||
if loaded.AutoplayNextTrack != nil {
|
||||
DefaultPreferences.AutoplayNextTrack = loaded.AutoplayNextTrack
|
||||
} else {
|
||||
DefaultPreferences.AutoplayNextTrack = &f
|
||||
}
|
||||
}
|
||||
|
||||
func boolean(in string) bool {
|
||||
|
||||
@@ -30,6 +30,10 @@ func Defaults(dst *cfg.Preferences) {
|
||||
if dst.ParseDescriptions == nil {
|
||||
dst.ParseDescriptions = cfg.DefaultPreferences.ParseDescriptions
|
||||
}
|
||||
|
||||
if dst.AutoplayNextTrack == nil {
|
||||
dst.AutoplayNextTrack = cfg.DefaultPreferences.AutoplayNextTrack
|
||||
}
|
||||
}
|
||||
|
||||
func Get(c *fiber.Ctx) (cfg.Preferences, error) {
|
||||
@@ -51,6 +55,7 @@ type PrefsForm struct {
|
||||
Player string
|
||||
ProxyStreams string
|
||||
FullyPreloadTrack string
|
||||
AutoplayNextTrack string
|
||||
}
|
||||
|
||||
func Load(r fiber.Router) {
|
||||
@@ -85,6 +90,12 @@ func Load(r fiber.Router) {
|
||||
} else if p.ProxyStreams == "" {
|
||||
old.ProxyStreams = &f
|
||||
}
|
||||
|
||||
if p.AutoplayNextTrack == "on" {
|
||||
old.AutoplayNextTrack = &t
|
||||
} else {
|
||||
old.AutoplayNextTrack = &f
|
||||
}
|
||||
}
|
||||
|
||||
if p.FullyPreloadTrack == "on" {
|
||||
|
||||
@@ -187,7 +187,7 @@ func (p *Playlist) GetMissingTracks() error {
|
||||
missing := []MissingTrack{}
|
||||
for i, track := range p.Tracks {
|
||||
if track.Title == "" {
|
||||
missing = append(missing, MissingTrack{ID: track.ID, Index: i})
|
||||
missing = append(missing, MissingTrack{ID: strconv.FormatInt(int64(track.IDint), 10), Index: i})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,3 +212,7 @@ func (p *Playlist) GetMissingTracks() error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p Playlist) Href() string {
|
||||
return "/" + p.Author.Permalink + "/sets/" + p.Permalink
|
||||
}
|
||||
|
||||
@@ -433,3 +433,7 @@ func (t Track) DownloadImage() ([]byte, string, error) {
|
||||
|
||||
return data, string(resp.Header.Peek("Content-Type")), nil
|
||||
}
|
||||
|
||||
func (t Track) Href() string {
|
||||
return "/" + t.Author.Permalink + "/" + t.Permalink
|
||||
}
|
||||
|
||||
33
main.go
33
main.go
@@ -34,7 +34,7 @@ func main() {
|
||||
app.Use(recover.New())
|
||||
app.Use(compress.New(compress.Config{Level: compress.LevelBestSpeed}))
|
||||
|
||||
app.Static("/", "assets", fiber.Static{Compress: true, MaxAge: 3600}) // 1hour
|
||||
app.Static("/", "assets", fiber.Static{Compress: true, MaxAge: 7200}) // 2 hours
|
||||
app.Static("/js/hls.js/", "node_modules/hls.js/dist", fiber.Static{Compress: true, MaxAge: 28800}) // 8 hours
|
||||
|
||||
// Just for easy inspection of cache in development. Since debug is constant, the compiler will just remove the code below if it's set to false, so this has no runtime overhead.
|
||||
@@ -222,6 +222,7 @@ func main() {
|
||||
ProxyImages bool
|
||||
ProxyStreams bool
|
||||
Restream bool
|
||||
GetWebProfiles bool
|
||||
DefaultPreferences cfg.Preferences
|
||||
}
|
||||
|
||||
@@ -230,6 +231,7 @@ func main() {
|
||||
ProxyImages: cfg.ProxyImages,
|
||||
ProxyStreams: cfg.ProxyStreams,
|
||||
Restream: cfg.Restream,
|
||||
GetWebProfiles: cfg.GetWebProfiles,
|
||||
DefaultPreferences: cfg.DefaultPreferences,
|
||||
})
|
||||
})
|
||||
@@ -365,8 +367,35 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
var playlist *sc.Playlist
|
||||
var nextTrack *sc.Track
|
||||
if pl := c.Query("playlist"); pl != "" {
|
||||
p, err := sc.GetPlaylist(pl)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("error getting %s playlist (track): %s\n", pl, err)
|
||||
return err
|
||||
}
|
||||
|
||||
nextIndex := -1
|
||||
for i, t := range p.Tracks {
|
||||
if t.ID == track.ID {
|
||||
nextIndex = i + 1
|
||||
}
|
||||
}
|
||||
|
||||
if nextIndex != -1 {
|
||||
if nextIndex == len(p.Tracks) {
|
||||
nextIndex = 0
|
||||
}
|
||||
|
||||
nextTrack = &p.Tracks[nextIndex]
|
||||
playlist = &p
|
||||
}
|
||||
}
|
||||
|
||||
c.Set("Content-Type", "text/html")
|
||||
return templates.Base(track.Title+" by "+track.Author.Username, templates.Track(prefs, track, stream, displayErr), templates.TrackHeader(prefs, track)).Render(context.Background(), c)
|
||||
return templates.Base(track.Title+" by "+track.Author.Username, templates.Track(prefs, track, stream, displayErr, c.Query("autoplay") == "true", playlist, nextTrack, c.Query("volume")), templates.TrackHeader(prefs, track)).Render(context.Background(), c)
|
||||
})
|
||||
|
||||
app.Get("/:user", func(c *fiber.Ctx) error {
|
||||
|
||||
@@ -13,7 +13,7 @@ templ FeaturedTracks(p *sc.Paginated[*sc.Track]) {
|
||||
<p>no more tracks</p>
|
||||
} else {
|
||||
for _, track := range p.Collection {
|
||||
@TrackItem(track, true)
|
||||
@TrackItem(track, true, "")
|
||||
}
|
||||
if p.Next != "" {
|
||||
<a class="btn" href={ templ.URL("/_/featured?pagination=" + url.QueryEscape(strings.Split(p.Next, "/all-music")[1])) } rel="noreferrer">more tracks</a>
|
||||
|
||||
@@ -16,8 +16,12 @@ templ PlaylistHeader(p sc.Playlist) {
|
||||
<link rel="icon" type="image/x-icon" href={ p.Artwork }/>
|
||||
}
|
||||
|
||||
func playlist(t sc.Track, p sc.Playlist) string {
|
||||
return t.Href() + "?playlist=" + p.Href()[1:]
|
||||
}
|
||||
|
||||
templ PlaylistItem(playlist *sc.Playlist, showUsername bool) {
|
||||
<a class="listing" href={ templ.URL("/" + playlist.Author.Permalink + "/sets/" + playlist.Permalink) }>
|
||||
<a class="listing" href={ templ.URL(playlist.Href()) }>
|
||||
if playlist.Artwork != "" {
|
||||
<img src={ playlist.Artwork }/>
|
||||
} else {
|
||||
@@ -40,7 +44,7 @@ templ Playlist(prefs cfg.Preferences, p sc.Playlist) {
|
||||
<h1>{ p.Title }</h1>
|
||||
@UserItem(&p.Author)
|
||||
<div style="display: flex;">
|
||||
<a class="btn" href={ templ.URL("https://soundcloud.com/" + p.Author.Permalink + "/sets/" + p.Permalink) }>view on soundcloud</a>
|
||||
<a class="btn" href={ templ.URL("https://soundcloud.com" + p.Href()) }>view on soundcloud</a>
|
||||
</div>
|
||||
<br/>
|
||||
@Description(prefs, p.Description, nil)
|
||||
@@ -49,7 +53,11 @@ templ Playlist(prefs cfg.Preferences, p sc.Playlist) {
|
||||
<br/>
|
||||
<div>
|
||||
for _, track := range p.Tracks {
|
||||
@TrackItem(&track, true)
|
||||
if *prefs.AutoplayNextTrack {
|
||||
@TrackItem(&track, true, playlist(track, p))
|
||||
} else {
|
||||
@TrackItem(&track, true, "")
|
||||
}
|
||||
}
|
||||
</div>
|
||||
if len(p.MissingTracks) != 0 {
|
||||
|
||||
@@ -17,17 +17,14 @@ type option struct {
|
||||
}
|
||||
|
||||
// i hate this
|
||||
// ^ outdated, i no longer hate this
|
||||
templ sel(name string, options []option, selected string) {
|
||||
<select name={ name } autocomplete="off">
|
||||
for _, opt := range options {
|
||||
if opt.value == selected {
|
||||
<option value={ opt.value } selected>{ opt.desc }</option>
|
||||
} else {
|
||||
if opt.disabled {
|
||||
<option value={ opt.value } disabled>{ opt.desc }</option>
|
||||
} else {
|
||||
<option value={ opt.value }>{ opt.desc }</option>
|
||||
}
|
||||
<option value={ opt.value } disabled?={ opt.disabled }>{ opt.desc }</option>
|
||||
}
|
||||
}
|
||||
</select>
|
||||
@@ -71,6 +68,11 @@ templ Preferences(prefs cfg.Preferences) {
|
||||
@checkbox("FullyPreloadTrack", *prefs.FullyPreloadTrack)
|
||||
</label>
|
||||
<br/>
|
||||
<label>
|
||||
Autoplay next track in playlists:
|
||||
@checkbox("AutoplayNextTrack", *prefs.AutoplayNextTrack)
|
||||
</label>
|
||||
<br/>
|
||||
}
|
||||
<input type="submit" value="Update" class="btn" style="margin-top: 1rem;"/>
|
||||
<br/>
|
||||
|
||||
@@ -19,15 +19,23 @@ templ TrackHeader(prefs cfg.Preferences, t sc.Track) {
|
||||
}
|
||||
}
|
||||
|
||||
templ TrackPlayer(prefs cfg.Preferences, track sc.Track, stream string, displayErr string) {
|
||||
func next(t *sc.Track, p *sc.Playlist) string {
|
||||
return t.Href() + "?autoplay=true&playlist=" + p.Href()[1:]
|
||||
}
|
||||
|
||||
templ TrackPlayer(prefs cfg.Preferences, track sc.Track, stream string, displayErr string, autoplay bool, nextTrack *sc.Track, playlist *sc.Playlist, volume string) {
|
||||
if *prefs.Player == cfg.NonePlayer {
|
||||
{{ return }}
|
||||
}
|
||||
if displayErr == "" {
|
||||
if cfg.Restream && *prefs.Player == cfg.RestreamPlayer {
|
||||
<audio src={ "/_/restream/" + track.Author.Permalink + "/" + track.Permalink } controls></audio>
|
||||
<audio src={ "/_/restream" + track.Href() } controls autoplay?={ autoplay }></audio>
|
||||
} else if stream != "" {
|
||||
<audio id="track" src={ stream } controls></audio>
|
||||
if nextTrack != nil {
|
||||
<audio id="track" src={ stream } controls autoplay?={ autoplay } data-next={next(nextTrack, playlist)} volume={volume}></audio>
|
||||
} else {
|
||||
<audio id="track" src={ stream } controls autoplay?={ autoplay }></audio>
|
||||
}
|
||||
if *prefs.FullyPreloadTrack {
|
||||
<script async src="/player_preload.js"></script>
|
||||
} else {
|
||||
@@ -49,9 +57,12 @@ templ TrackPlayer(prefs cfg.Preferences, track sc.Track, stream string, displayE
|
||||
}
|
||||
}
|
||||
|
||||
templ TrackItem(track *sc.Track, showUsername bool) {
|
||||
templ TrackItem(track *sc.Track, showUsername bool, overrideHref string) {
|
||||
if track.Title != "" {
|
||||
<a class="listing" href={ templ.URL("/" + track.Author.Permalink + "/" + track.Permalink) }>
|
||||
{{ if overrideHref == "" {
|
||||
overrideHref = track.Href()
|
||||
} }}
|
||||
<a class="listing" href={ templ.URL(overrideHref) }>
|
||||
if track.Artwork != "" {
|
||||
<img src={ track.Artwork }/>
|
||||
} else {
|
||||
@@ -67,24 +78,36 @@ templ TrackItem(track *sc.Track, showUsername bool) {
|
||||
}
|
||||
}
|
||||
|
||||
templ Track(prefs cfg.Preferences, t sc.Track, stream string, displayErr string) {
|
||||
templ Track(prefs cfg.Preferences, t sc.Track, stream string, displayErr string, autoplay bool, playlist *sc.Playlist, nextTrack *sc.Track, volume string) {
|
||||
if t.Artwork != "" {
|
||||
<img src={ t.Artwork } width="300px"/>
|
||||
}
|
||||
<h1>{ t.Title }</h1>
|
||||
@TrackPlayer(prefs, t, stream, displayErr)
|
||||
@TrackPlayer(prefs, t, stream, displayErr, autoplay, nextTrack, playlist, volume)
|
||||
if t.Genre != "" {
|
||||
<p class="tag">{ t.Genre }</p>
|
||||
} else {
|
||||
<br/>
|
||||
<br/>
|
||||
}
|
||||
if playlist != nil {
|
||||
<details style="margin-bottom: 1rem;">
|
||||
<summary>Playback info</summary>
|
||||
|
||||
<h2>In playlist:</h2>
|
||||
@PlaylistItem(playlist, true)
|
||||
|
||||
<h2>Next track:</h2>
|
||||
@TrackItem(nextTrack, true, next(nextTrack, playlist) + "&volume=" + volume)
|
||||
|
||||
<a href={templ.URL(t.Href())} class="link">Stop playlist playback</a>
|
||||
</details>
|
||||
}
|
||||
@UserItem(&t.Author)
|
||||
//<div class="btns">
|
||||
<div style="display: flex; gap: 1rem">
|
||||
<a class="btn" href={ templ.URL("https://soundcloud.com/" + t.Author.Permalink + "/" + t.Permalink) }>view on soundcloud</a>
|
||||
<a class="btn" href={ templ.URL("https://soundcloud.com" + t.Href()) }>view on soundcloud</a>
|
||||
if cfg.Restream {
|
||||
<a class="btn" href={ templ.URL("/_/restream/" + t.Author.Permalink + "/" + t.Permalink + "?metadata=true") } download={ t.Author.Username + " - " + t.Title + ".mp3" }>download</a>
|
||||
<a class="btn" href={ templ.URL("/_/restream" + t.Href() + "?metadata=true") } download={ t.Author.Username + " - " + t.Title + ".mp3" }>download</a>
|
||||
}
|
||||
</div>
|
||||
<br/>
|
||||
@@ -119,7 +142,7 @@ templ TrackEmbed(prefs cfg.Preferences, t sc.Track, stream string, displayErr st
|
||||
<img src={ t.Artwork } width="300px"/>
|
||||
}
|
||||
<h1>{ t.Title }</h1>
|
||||
@TrackPlayer(prefs, t, stream, displayErr)
|
||||
@TrackPlayer(prefs, t, stream, displayErr, false, nil, nil, "")
|
||||
@UserItem(&t.Author)
|
||||
</body>
|
||||
</html>
|
||||
@@ -133,7 +156,7 @@ templ SearchTracks(p *sc.Paginated[*sc.Track]) {
|
||||
<p>no more results</p>
|
||||
} else {
|
||||
for _, track := range p.Collection {
|
||||
@TrackItem(track, true)
|
||||
@TrackItem(track, true, "")
|
||||
}
|
||||
if p.Next != "" && len(p.Collection) != int(p.Total) {
|
||||
<a class="btn" href={ templ.URL("?type=tracks&pagination=" + url.QueryEscape(strings.Split(p.Next, "/tracks")[1])) } rel="noreferrer">more tracks</a>
|
||||
|
||||
@@ -105,7 +105,7 @@ templ User(prefs cfg.Preferences, u sc.User, p *sc.Paginated[*sc.Track]) {
|
||||
if len(p.Collection) != 0 {
|
||||
<div>
|
||||
for _, track := range p.Collection {
|
||||
@TrackItem(track, false)
|
||||
@TrackItem(track, false, "")
|
||||
}
|
||||
</div>
|
||||
if p.Next != "" && len(p.Collection) != int(u.Tracks) {
|
||||
@@ -160,7 +160,7 @@ templ UserReposts(prefs cfg.Preferences, u sc.User, p *sc.Paginated[*sc.Repost])
|
||||
<div>
|
||||
for _, repost := range p.Collection {
|
||||
if repost.Type == sc.TrackRepost && repost.Track != nil {
|
||||
@TrackItem(repost.Track, true)
|
||||
@TrackItem(repost.Track, true, "")
|
||||
} else if repost.Type == sc.PlaylistRepost && repost.Playlist != nil {
|
||||
@PlaylistItem(repost.Playlist, true)
|
||||
}
|
||||
@@ -182,7 +182,7 @@ templ UserLikes(prefs cfg.Preferences, u sc.User, p *sc.Paginated[*sc.Like]) {
|
||||
<div>
|
||||
for _, like := range p.Collection {
|
||||
if like.Track != nil {
|
||||
@TrackItem(like.Track, true)
|
||||
@TrackItem(like.Track, true, "")
|
||||
} else if like.Playlist != nil {
|
||||
@PlaylistItem(like.Playlist, true)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user