mirror of
https://git.maid.zone/stuff/soundcloak.git
synced 2025-12-10 05:39:38 +05:00
KeepPlayerFocus pref, rework prefs page, always autoplay if you click on the next track
This commit is contained in:
@@ -1,22 +1,16 @@
|
|||||||
# Preferences
|
# Preferences
|
||||||
|
|
||||||
|
|
||||||
| 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 |
|
|
||||||
| 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 |
|
| 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 |
|
# Player preferences
|
||||||
| 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 |
|
| Name | Key | Default | Possible values | Description |
|
||||||
| 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 | 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
|
## HLS Player
|
||||||
|
|
||||||
### HLS Player
|
|
||||||
|
|
||||||
| Name | Key | Default | Possible values | Description |
|
| Name | Key | Default | Possible values | Description |
|
||||||
| :-------------------- | ------------------- | ---------------------------------------- | ----------------- | :------------------------------------------------------------------------------------ |
|
| :-------------------- | ------------------- | ---------------------------------------- | ----------------- | :------------------------------------------------------------------------------------ |
|
||||||
@@ -24,9 +18,30 @@
|
|||||||
| Fully preload track | FullyPreloadTrack | false | true, false | Fully load track when the page is loaded (track stream expires in ~5 minutes) |
|
| Fully preload track | FullyPreloadTrack | false | true, false | Fully load track when the page is loaded (track stream expires in ~5 minutes) |
|
||||||
| Streaming audio | HLSAudio | "mpeg" | "mpeg", "aac" | What [audio preset](AUDIO_PRESETS.md) should be loaded when streaming audio |
|
| Streaming audio | HLSAudio | "mpeg" | "mpeg", "aac" | What [audio preset](AUDIO_PRESETS.md) should be loaded when streaming audio |
|
||||||
|
|
||||||
### Restream Player
|
## Restream Player
|
||||||
|
|
||||||
|
|
||||||
| Name | Key | Default | Possible values | Description |
|
| Name | Key | Default | Possible values | Description |
|
||||||
| :---------------- | --------------- | --------- | ------------------------------- | :--------------------------------------------------------------------------- |
|
| :---------------- | --------------- | --------- | ------------------------------- | :--------------------------------------------------------------------------- |
|
||||||
| Streaming audio | RestreamAudio | "mpeg" | "mpeg", "opus", "aac", "best" | What [audio preset](AUDIO_PRESETS.md) should be loaded when streaming audio |
|
| Streaming audio | RestreamAudio | "mpeg" | "mpeg", "opus", "aac", "best" | What [audio preset](AUDIO_PRESETS.md) should be loaded when streaming audio |
|
||||||
|
|
||||||
|
|
||||||
|
# Frontend enhancements
|
||||||
|
|
||||||
|
| Name | Key | Default | Possible values | Description |
|
||||||
|
| :--------------------------------- | --------------------- | ------------------------------------------------------------------------ | ------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| Proxy images | ProxyImages | same as ProxyImages in backend config | true, false | Proxy images through the backend. ProxyImages must be enabled on the backend |
|
||||||
|
| 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 |
|
||||||
|
| 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
|
||||||
|
| Keep player focus | KeepPlayerFocus | false | true, false | Always keep track element in focus, so you can control it with keyboard. Requires JS |
|
||||||
|
|
||||||
|
## Autoplay
|
||||||
|
*Requires JS. You also need to allow autoplay from this domain*
|
||||||
|
|
||||||
|
| Name | Key | Default | Possible values | Description |
|
||||||
|
| :--------------------------------- | --------------------- | ------------------------------------------------------------------------ | ------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| Autoplay next track in playlists | AutoplayNextTrack | false | true, false | Automatically start playlist playback when you open a track from the playlist. |
|
||||||
|
| 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.
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
package cfg
|
package cfg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -144,6 +143,7 @@ func defaultPreferences() {
|
|||||||
|
|
||||||
DefaultPreferences.SearchSuggestions = &False
|
DefaultPreferences.SearchSuggestions = &False
|
||||||
DefaultPreferences.DynamicLoadComments = &False
|
DefaultPreferences.DynamicLoadComments = &False
|
||||||
|
DefaultPreferences.KeepPlayerFocus = &False
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadDefaultPreferences(loaded Preferences) {
|
func loadDefaultPreferences(loaded Preferences) {
|
||||||
@@ -238,21 +238,18 @@ func loadDefaultPreferences(loaded Preferences) {
|
|||||||
} else {
|
} else {
|
||||||
DefaultPreferences.DynamicLoadComments = &False
|
DefaultPreferences.DynamicLoadComments = &False
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if loaded.KeepPlayerFocus != nil {
|
||||||
|
DefaultPreferences.KeepPlayerFocus = loaded.KeepPlayerFocus
|
||||||
|
} else {
|
||||||
|
DefaultPreferences.KeepPlayerFocus = &False
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func boolean(in string) bool {
|
func boolean(in string) bool {
|
||||||
return strings.Trim(strings.ToLower(in), " ") == "true"
|
return strings.Trim(strings.ToLower(in), " ") == "true"
|
||||||
}
|
}
|
||||||
|
|
||||||
type wrappedError struct {
|
|
||||||
err error
|
|
||||||
fault string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w wrappedError) Error() string {
|
|
||||||
return fmt.Sprintf("error loading %s: %s", w.fault, w.err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func fromEnv() error {
|
func fromEnv() error {
|
||||||
env := os.Getenv("GET_WEB_PROFILES")
|
env := os.Getenv("GET_WEB_PROFILES")
|
||||||
if env != "" {
|
if env != "" {
|
||||||
@@ -264,7 +261,7 @@ func fromEnv() error {
|
|||||||
var p Preferences
|
var p Preferences
|
||||||
err := json.Unmarshal(S2b(env), &p)
|
err := json.Unmarshal(S2b(env), &p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return wrappedError{err, "DEFAULT_PREFERENCES"}
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
loadDefaultPreferences(p)
|
loadDefaultPreferences(p)
|
||||||
@@ -306,7 +303,7 @@ func fromEnv() error {
|
|||||||
if env != "" {
|
if env != "" {
|
||||||
num, err := strconv.ParseInt(env, 10, 64)
|
num, err := strconv.ParseInt(env, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return wrappedError{err, "CLIENT_ID_TTL"}
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
ClientIDTTL = time.Duration(num) * time.Second
|
ClientIDTTL = time.Duration(num) * time.Second
|
||||||
@@ -316,7 +313,7 @@ func fromEnv() error {
|
|||||||
if env != "" {
|
if env != "" {
|
||||||
num, err := strconv.ParseInt(env, 10, 64)
|
num, err := strconv.ParseInt(env, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return wrappedError{err, "USER_TTL"}
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
UserTTL = time.Duration(num) * time.Second
|
UserTTL = time.Duration(num) * time.Second
|
||||||
@@ -326,7 +323,7 @@ func fromEnv() error {
|
|||||||
if env != "" {
|
if env != "" {
|
||||||
num, err := strconv.ParseInt(env, 10, 64)
|
num, err := strconv.ParseInt(env, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return wrappedError{err, "USER_CACHE_CLEAN_DELAY"}
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
UserCacheCleanDelay = time.Duration(num) * time.Second
|
UserCacheCleanDelay = time.Duration(num) * time.Second
|
||||||
@@ -336,7 +333,7 @@ func fromEnv() error {
|
|||||||
if env != "" {
|
if env != "" {
|
||||||
num, err := strconv.ParseInt(env, 10, 64)
|
num, err := strconv.ParseInt(env, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return wrappedError{err, "TRACK_TTL"}
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
TrackTTL = time.Duration(num) * time.Second
|
TrackTTL = time.Duration(num) * time.Second
|
||||||
@@ -346,7 +343,7 @@ func fromEnv() error {
|
|||||||
if env != "" {
|
if env != "" {
|
||||||
num, err := strconv.ParseInt(env, 10, 64)
|
num, err := strconv.ParseInt(env, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return wrappedError{err, "TRACK_CACHE_CLEAN_DELAY"}
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
TrackCacheCleanDelay = time.Duration(num) * time.Second
|
TrackCacheCleanDelay = time.Duration(num) * time.Second
|
||||||
@@ -356,7 +353,7 @@ func fromEnv() error {
|
|||||||
if env != "" {
|
if env != "" {
|
||||||
num, err := strconv.ParseInt(env, 10, 64)
|
num, err := strconv.ParseInt(env, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return wrappedError{err, "PLAYLIST_TTL"}
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
PlaylistTTL = time.Duration(num) * time.Second
|
PlaylistTTL = time.Duration(num) * time.Second
|
||||||
@@ -366,7 +363,7 @@ func fromEnv() error {
|
|||||||
if env != "" {
|
if env != "" {
|
||||||
num, err := strconv.ParseInt(env, 10, 64)
|
num, err := strconv.ParseInt(env, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return wrappedError{err, "PLAYLIST_CACHE_CLEAN_DELAY"}
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
PlaylistCacheCleanDelay = time.Duration(num) * time.Second
|
PlaylistCacheCleanDelay = time.Duration(num) * time.Second
|
||||||
@@ -381,7 +378,7 @@ func fromEnv() error {
|
|||||||
if env != "" {
|
if env != "" {
|
||||||
num, err := strconv.ParseInt(env, 10, 64)
|
num, err := strconv.ParseInt(env, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return wrappedError{err, "DNS_CACHE_TTL"}
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
DNSCacheTTL = time.Duration(num) * time.Second
|
DNSCacheTTL = time.Duration(num) * time.Second
|
||||||
@@ -412,7 +409,7 @@ func fromEnv() error {
|
|||||||
var p []string
|
var p []string
|
||||||
err := json.Unmarshal(S2b(env), &p)
|
err := json.Unmarshal(S2b(env), &p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return wrappedError{err, "TRUSTED_PROXIES"}
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
TrustedProxies = p
|
TrustedProxies = p
|
||||||
@@ -436,12 +433,8 @@ func init() {
|
|||||||
if env := os.Getenv("SOUNDCLOAK_CONFIG"); env == "FROM_ENV" {
|
if env := os.Getenv("SOUNDCLOAK_CONFIG"); env == "FROM_ENV" {
|
||||||
err := fromEnv()
|
err := fromEnv()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// So we only set default preferences if it fails to load that in
|
log.Println("Warning: failed to load config from environment:", err)
|
||||||
if err.(wrappedError).fault == "DEFAULT_PREFERENCES" {
|
defaultPreferences()
|
||||||
defaultPreferences()
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("failed to load config from environment:", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -451,7 +444,7 @@ func init() {
|
|||||||
|
|
||||||
data, err := os.ReadFile(filename)
|
data, err := os.ReadFile(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("failed to load config from %s: %s\n", filename, err)
|
log.Printf("Warning: failed to load config from %s: %s\n", filename, err)
|
||||||
defaultPreferences()
|
defaultPreferences()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,6 +80,8 @@ type Preferences struct {
|
|||||||
SearchSuggestions *bool // load search suggestions on main page
|
SearchSuggestions *bool // load search suggestions on main page
|
||||||
|
|
||||||
DynamicLoadComments *bool // dynamic comments loader without leaving track page
|
DynamicLoadComments *bool // dynamic comments loader without leaving track page
|
||||||
|
|
||||||
|
KeepPlayerFocus *bool // keep player element in focus
|
||||||
}
|
}
|
||||||
|
|
||||||
func B2s(b []byte) string {
|
func B2s(b []byte) string {
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import (
|
|||||||
"github.com/gofiber/fiber/v3"
|
"github.com/gofiber/fiber/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const on = "on"
|
||||||
|
|
||||||
func Defaults(dst *cfg.Preferences) {
|
func Defaults(dst *cfg.Preferences) {
|
||||||
if dst.Player == nil {
|
if dst.Player == nil {
|
||||||
dst.Player = cfg.DefaultPreferences.Player
|
dst.Player = cfg.DefaultPreferences.Player
|
||||||
@@ -67,6 +69,10 @@ func Defaults(dst *cfg.Preferences) {
|
|||||||
if dst.DynamicLoadComments == nil {
|
if dst.DynamicLoadComments == nil {
|
||||||
dst.DynamicLoadComments = cfg.DefaultPreferences.DynamicLoadComments
|
dst.DynamicLoadComments = cfg.DefaultPreferences.DynamicLoadComments
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if dst.KeepPlayerFocus == nil {
|
||||||
|
dst.KeepPlayerFocus = cfg.DefaultPreferences.KeepPlayerFocus
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Get(c fiber.Ctx) (cfg.Preferences, error) {
|
func Get(c fiber.Ctx) (cfg.Preferences, error) {
|
||||||
@@ -97,6 +103,7 @@ type PrefsForm struct {
|
|||||||
ShowAudio string
|
ShowAudio string
|
||||||
SearchSuggestions string
|
SearchSuggestions string
|
||||||
DynamicLoadComments string
|
DynamicLoadComments string
|
||||||
|
KeepPlayerFocus string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Export struct {
|
type Export struct {
|
||||||
@@ -130,19 +137,19 @@ func Load(r *fiber.App) {
|
|||||||
old.DefaultAutoplayMode = &p.DefaultAutoplayMode
|
old.DefaultAutoplayMode = &p.DefaultAutoplayMode
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.AutoplayNextTrack == "on" {
|
if p.AutoplayNextTrack == on {
|
||||||
old.AutoplayNextTrack = &cfg.True
|
old.AutoplayNextTrack = &cfg.True
|
||||||
} else {
|
} else {
|
||||||
old.AutoplayNextTrack = &cfg.False
|
old.AutoplayNextTrack = &cfg.False
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.AutoplayNextRelatedTrack == "on" {
|
if p.AutoplayNextRelatedTrack == on {
|
||||||
old.AutoplayNextRelatedTrack = &cfg.True
|
old.AutoplayNextRelatedTrack = &cfg.True
|
||||||
} else {
|
} else {
|
||||||
old.AutoplayNextRelatedTrack = &cfg.False
|
old.AutoplayNextRelatedTrack = &cfg.False
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.ShowAudio == "on" {
|
if p.ShowAudio == on {
|
||||||
old.ShowAudio = &cfg.True
|
old.ShowAudio = &cfg.True
|
||||||
} else {
|
} else {
|
||||||
old.ShowAudio = &cfg.False
|
old.ShowAudio = &cfg.False
|
||||||
@@ -150,14 +157,14 @@ func Load(r *fiber.App) {
|
|||||||
|
|
||||||
if *old.Player == cfg.HLSPlayer {
|
if *old.Player == cfg.HLSPlayer {
|
||||||
if cfg.ProxyStreams {
|
if cfg.ProxyStreams {
|
||||||
if p.ProxyStreams == "on" {
|
if p.ProxyStreams == on {
|
||||||
old.ProxyStreams = &cfg.True
|
old.ProxyStreams = &cfg.True
|
||||||
} else if p.ProxyStreams == "" {
|
} else if p.ProxyStreams == "" {
|
||||||
old.ProxyStreams = &cfg.False
|
old.ProxyStreams = &cfg.False
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.FullyPreloadTrack == "on" {
|
if p.FullyPreloadTrack == on {
|
||||||
old.FullyPreloadTrack = &cfg.True
|
old.FullyPreloadTrack = &cfg.True
|
||||||
} else if p.FullyPreloadTrack == "" {
|
} else if p.FullyPreloadTrack == "" {
|
||||||
old.FullyPreloadTrack = &cfg.False
|
old.FullyPreloadTrack = &cfg.False
|
||||||
@@ -175,31 +182,37 @@ func Load(r *fiber.App) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if cfg.ProxyImages {
|
if cfg.ProxyImages {
|
||||||
if p.ProxyImages == "on" {
|
if p.ProxyImages == on {
|
||||||
old.ProxyImages = &cfg.True
|
old.ProxyImages = &cfg.True
|
||||||
} else if p.ProxyImages == "" {
|
} else if p.ProxyImages == "" {
|
||||||
old.ProxyImages = &cfg.False
|
old.ProxyImages = &cfg.False
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.ParseDescriptions == "on" {
|
if p.ParseDescriptions == on {
|
||||||
old.ParseDescriptions = &cfg.True
|
old.ParseDescriptions = &cfg.True
|
||||||
} else {
|
} else {
|
||||||
old.ParseDescriptions = &cfg.False
|
old.ParseDescriptions = &cfg.False
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.SearchSuggestions == "on" {
|
if p.SearchSuggestions == on {
|
||||||
old.SearchSuggestions = &cfg.True
|
old.SearchSuggestions = &cfg.True
|
||||||
} else {
|
} else {
|
||||||
old.SearchSuggestions = &cfg.False
|
old.SearchSuggestions = &cfg.False
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.DynamicLoadComments == "on" {
|
if p.DynamicLoadComments == on {
|
||||||
old.DynamicLoadComments = &cfg.True
|
old.DynamicLoadComments = &cfg.True
|
||||||
} else {
|
} else {
|
||||||
old.DynamicLoadComments = &cfg.False
|
old.DynamicLoadComments = &cfg.False
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if p.KeepPlayerFocus == on {
|
||||||
|
old.KeepPlayerFocus = &cfg.True
|
||||||
|
} else {
|
||||||
|
old.KeepPlayerFocus = &cfg.False
|
||||||
|
}
|
||||||
|
|
||||||
old.Player = &p.Player
|
old.Player = &p.Player
|
||||||
|
|
||||||
data, err := json.Marshal(old)
|
data, err := json.Marshal(old)
|
||||||
|
|||||||
9
static/assets/keepfocus.js
Normal file
9
static/assets/keepfocus.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
var audio = document.getElementById('track');
|
||||||
|
audio.onblur = function (e) {
|
||||||
|
if (e.target != e.relatedTarget) {
|
||||||
|
setTimeout(function() {
|
||||||
|
e.target.focus({preventScroll: true, focusVisible: false});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
audio.focus({focusVisible: false});
|
||||||
@@ -42,55 +42,13 @@ templ sel_audio(name string, selected string, noOpus bool) {
|
|||||||
templ Preferences(prefs cfg.Preferences) {
|
templ Preferences(prefs cfg.Preferences) {
|
||||||
<h1>Preferences</h1>
|
<h1>Preferences</h1>
|
||||||
<form method="post" autocomplete="off">
|
<form method="post" autocomplete="off">
|
||||||
<label>
|
|
||||||
Parse descriptions:
|
|
||||||
@checkbox("ParseDescriptions", *prefs.ParseDescriptions)
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Show current audio:
|
|
||||||
@checkbox("ShowAudio", *prefs.ShowAudio)
|
|
||||||
</label>
|
|
||||||
if cfg.ProxyImages {
|
|
||||||
<label>
|
|
||||||
Proxy images:
|
|
||||||
@checkbox("ProxyImages", *prefs.ProxyImages)
|
|
||||||
</label>
|
|
||||||
}
|
|
||||||
if cfg.Restream {
|
if cfg.Restream {
|
||||||
<label>
|
<label>
|
||||||
Download audio:
|
Download audio:
|
||||||
@sel_audio("DownloadAudio", *prefs.DownloadAudio, false)
|
@sel_audio("DownloadAudio", *prefs.DownloadAudio, false)
|
||||||
</label>
|
</label>
|
||||||
}
|
}
|
||||||
<label>
|
<h1>Player preferences</h1>
|
||||||
Autoplay next track in playlists:
|
|
||||||
@checkbox("AutoplayNextTrack", *prefs.AutoplayNextTrack)
|
|
||||||
(requires JS; you need to allow autoplay from this domain!!)
|
|
||||||
</label>
|
|
||||||
if *prefs.AutoplayNextTrack {
|
|
||||||
<label>
|
|
||||||
Default autoplay mode (in playlists):
|
|
||||||
@sel("DefaultAutoplayMode", []option{
|
|
||||||
{"normal", "Normal (play songs in order)", false},
|
|
||||||
{"random", "Random (play random song)", false},
|
|
||||||
}, *prefs.DefaultAutoplayMode)
|
|
||||||
</label>
|
|
||||||
}
|
|
||||||
<label>
|
|
||||||
Autoplay next related track:
|
|
||||||
@checkbox("AutoplayNextRelatedTrack", *prefs.AutoplayNextRelatedTrack)
|
|
||||||
(requires JS; you need to allow autoplay from this domain!!)
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Fetch search suggestions:
|
|
||||||
@checkbox("SearchSuggestions", *prefs.SearchSuggestions)
|
|
||||||
(requires JS)
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Dynamically load comments:
|
|
||||||
@checkbox("DynamicLoadComments", *prefs.DynamicLoadComments)
|
|
||||||
(requires JS)
|
|
||||||
</label>
|
|
||||||
<label>
|
<label>
|
||||||
Player:
|
Player:
|
||||||
@sel("Player", []option{
|
@sel("Player", []option{
|
||||||
@@ -101,7 +59,6 @@ templ Preferences(prefs cfg.Preferences) {
|
|||||||
</label>
|
</label>
|
||||||
switch *prefs.Player {
|
switch *prefs.Player {
|
||||||
case cfg.HLSPlayer:
|
case cfg.HLSPlayer:
|
||||||
<h1>Player-specific preferences</h1>
|
|
||||||
if cfg.ProxyStreams {
|
if cfg.ProxyStreams {
|
||||||
<label>
|
<label>
|
||||||
Proxy song streams:
|
Proxy song streams:
|
||||||
@@ -117,12 +74,60 @@ templ Preferences(prefs cfg.Preferences) {
|
|||||||
@sel_audio("HLSAudio", *prefs.HLSAudio, true)
|
@sel_audio("HLSAudio", *prefs.HLSAudio, true)
|
||||||
</label>
|
</label>
|
||||||
case cfg.RestreamPlayer:
|
case cfg.RestreamPlayer:
|
||||||
<h1>Player-specific preferences</h1>
|
|
||||||
<label>
|
<label>
|
||||||
Streaming audio:
|
Streaming audio:
|
||||||
@sel_audio("RestreamAudio", *prefs.RestreamAudio, false)
|
@sel_audio("RestreamAudio", *prefs.RestreamAudio, false)
|
||||||
</label>
|
</label>
|
||||||
}
|
}
|
||||||
|
<h1>Frontend enhancements</h1>
|
||||||
|
if cfg.ProxyImages {
|
||||||
|
<label>
|
||||||
|
Proxy images:
|
||||||
|
@checkbox("ProxyImages", *prefs.ProxyImages)
|
||||||
|
</label>
|
||||||
|
}
|
||||||
|
<label>
|
||||||
|
Parse descriptions:
|
||||||
|
@checkbox("ParseDescriptions", *prefs.ParseDescriptions)
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Show current audio:
|
||||||
|
@checkbox("ShowAudio", *prefs.ShowAudio)
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Fetch search suggestions:
|
||||||
|
@checkbox("SearchSuggestions", *prefs.SearchSuggestions)
|
||||||
|
(requires JS)
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Dynamically load comments:
|
||||||
|
@checkbox("DynamicLoadComments", *prefs.DynamicLoadComments)
|
||||||
|
(requires JS)
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Keep player focus:
|
||||||
|
@checkbox("KeepPlayerFocus", *prefs.KeepPlayerFocus)
|
||||||
|
(requires JS)
|
||||||
|
</label>
|
||||||
|
<h2 style="margin-bottom: .35rem">Autoplay</h2>
|
||||||
|
<i>Requires JS. You also need to allow autoplay from this domain</i>
|
||||||
|
<label style="margin-top: 1rem">
|
||||||
|
Autoplay next track in playlists:
|
||||||
|
@checkbox("AutoplayNextTrack", *prefs.AutoplayNextTrack)
|
||||||
|
</label>
|
||||||
|
if *prefs.AutoplayNextTrack {
|
||||||
|
<label>
|
||||||
|
Default autoplay mode (in playlists):
|
||||||
|
@sel("DefaultAutoplayMode", []option{
|
||||||
|
{"normal", "Normal (play songs in order)", false},
|
||||||
|
{"random", "Random (play random song)", false},
|
||||||
|
}, *prefs.DefaultAutoplayMode)
|
||||||
|
</label>
|
||||||
|
}
|
||||||
|
<label>
|
||||||
|
Autoplay next related track:
|
||||||
|
@checkbox("AutoplayNextRelatedTrack", *prefs.AutoplayNextRelatedTrack)
|
||||||
|
</label>
|
||||||
<input type="submit" value="Update" class="btn" style="margin-top: 1rem;"/>
|
<input type="submit" value="Update" class="btn" style="margin-top: 1rem;"/>
|
||||||
<p>These preferences get saved in a cookie.</p>
|
<p>These preferences get saved in a cookie.</p>
|
||||||
</form>
|
</form>
|
||||||
@@ -137,6 +142,5 @@ templ Preferences(prefs cfg.Preferences) {
|
|||||||
<input class="btn" type="file" autocomplete="off" name="prefs"/>
|
<input class="btn" type="file" autocomplete="off" name="prefs"/>
|
||||||
<input type="submit" value="Import" class="btn"/>
|
<input type="submit" value="Import" class="btn"/>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<style>label{display:flex;gap:.5rem;align-items:center;margin-bottom:.35rem}</style>
|
<style>label{display:flex;gap:.5rem;align-items:center;margin-bottom:.35rem}</style>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,38 +47,36 @@ templ TrackHeader(prefs cfg.Preferences, t sc.Track, needPlayer bool) {
|
|||||||
<meta name="og:description" content={ t.FormatDescription() }/>
|
<meta name="og:description" content={ t.FormatDescription() }/>
|
||||||
<meta name="og:image" content={ t.Artwork }/>
|
<meta name="og:image" content={ t.Artwork }/>
|
||||||
<link rel="icon" type="image/x-icon" href={ t.Artwork }/>
|
<link rel="icon" type="image/x-icon" href={ t.Artwork }/>
|
||||||
if needPlayer && *prefs.Player == cfg.HLSPlayer {
|
if needPlayer {
|
||||||
<script src="/_/static/js/hls.light.min.js"></script>
|
if *prefs.Player == cfg.HLSPlayer {
|
||||||
|
<script src="/_/static/js/hls.light.min.js"></script>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func next(c *sc.Track, t *sc.Track, p *sc.Playlist, autoplay bool, mode string, volume string) string {
|
func next(c *sc.Track, t *sc.Track, p *sc.Playlist, mode string, volume string) string {
|
||||||
r := t.Href()
|
r := t.Href()
|
||||||
|
|
||||||
if p != nil {
|
if p != nil {
|
||||||
r += "?playlist=" + p.Href()[1:]
|
r += "?playlist=" + p.Href()[1:]
|
||||||
}
|
if mode != "" {
|
||||||
|
r += "&mode=" + mode
|
||||||
if autoplay {
|
}
|
||||||
if p == nil {
|
r += "&"
|
||||||
r += "?"
|
} else {
|
||||||
} else {
|
r += "?"
|
||||||
r += "&"
|
|
||||||
|
if c != nil {
|
||||||
|
r += "prev=" + string(c.ID) + "&"
|
||||||
}
|
}
|
||||||
r += "autoplay=true"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if mode != "" {
|
r += "autoplay=true"
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,19 +85,22 @@ templ TrackPlayer(prefs cfg.Preferences, track sc.Track, stream string, displayE
|
|||||||
{{ return }}
|
{{ return }}
|
||||||
}
|
}
|
||||||
if displayErr == "" {
|
if displayErr == "" {
|
||||||
{{ var audioPref string }}
|
{{ var audioPref *string }}
|
||||||
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(&track, nextTrack, playlist, true, mode, "") } volume={ volume }></audio>
|
<audio id="track" src={ "/_/restream" + track.Href() } controls autoplay?={ autoplay } data-next={ next(&track, nextTrack, playlist, 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 id="track" src={ "/_/restream" + track.Href() } controls autoplay?={ autoplay }></audio>
|
||||||
|
}
|
||||||
|
if *prefs.KeepPlayerFocus {
|
||||||
|
<script async src="/_/static/keepfocus.js"></script>
|
||||||
}
|
}
|
||||||
} 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(&track, nextTrack, playlist, true, mode, "") } volume={ volume }></audio>
|
<audio id="track" src={ stream } controls autoplay?={ autoplay } data-next={ next(&track, nextTrack, playlist, mode, "") } volume={ volume }></audio>
|
||||||
} else {
|
} else {
|
||||||
<audio id="track" src={ stream } controls autoplay?={ autoplay }></audio>
|
<audio id="track" src={ stream } controls autoplay?={ autoplay }></audio>
|
||||||
}
|
}
|
||||||
@@ -108,6 +109,9 @@ templ TrackPlayer(prefs cfg.Preferences, track sc.Track, stream string, displayE
|
|||||||
} else {
|
} else {
|
||||||
<script async src="/_/static/player.js"></script>
|
<script async src="/_/static/player.js"></script>
|
||||||
}
|
}
|
||||||
|
if *prefs.KeepPlayerFocus {
|
||||||
|
<script async src="/_/static/keepfocus.js"></script>
|
||||||
|
}
|
||||||
<noscript>
|
<noscript>
|
||||||
<br/>
|
<br/>
|
||||||
JavaScript is disabled! Audio playback may not work without it enabled.
|
JavaScript is disabled! Audio playback may not work without it enabled.
|
||||||
@@ -124,7 +128,7 @@ templ TrackPlayer(prefs cfg.Preferences, track sc.Track, stream string, displayE
|
|||||||
}
|
}
|
||||||
if *prefs.ShowAudio {
|
if *prefs.ShowAudio {
|
||||||
<div>
|
<div>
|
||||||
if audioPref == cfg.AudioBest {
|
if *audioPref == cfg.AudioBest {
|
||||||
<p>Audio: best ({ audio })</p>
|
<p>Audio: best ({ audio })</p>
|
||||||
} else {
|
} else {
|
||||||
<p>Audio: { audio }</p>
|
<p>Audio: { audio }</p>
|
||||||
@@ -178,25 +182,23 @@ templ Track(prefs cfg.Preferences, t sc.Track, stream string, displayErr string,
|
|||||||
if nextTrack != nil {
|
if nextTrack != nil {
|
||||||
<details open style="margin-bottom: 1rem;">
|
<details open style="margin-bottom: 1rem;">
|
||||||
<summary>Playback info</summary>
|
<summary>Playback info</summary>
|
||||||
|
|
||||||
if playlist != nil {
|
if playlist != nil {
|
||||||
<h2>In playlist:</h2>
|
<h2>In playlist:</h2>
|
||||||
@PlaylistItem(playlist, true)
|
@PlaylistItem(playlist, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
<h2>Next track:</h2>
|
<h2>Next track:</h2>
|
||||||
@TrackItem(nextTrack, true, next(&t, nextTrack, playlist, true, mode, volume))
|
@TrackItem(nextTrack, true, next(&t, nextTrack, playlist, mode, volume))
|
||||||
<div style="display: flex; gap: 1rem">
|
<div style="display: flex; gap: 1rem">
|
||||||
if playlist != nil {
|
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(nil, &t, playlist, false, cfg.AutoplayRandom, volume)) } class="btn">Switch to random mode</a>
|
<a href={ templ.SafeURL(next(nil, &t, playlist, cfg.AutoplayRandom, volume)) } class="btn">Switch to random mode</a>
|
||||||
|
} else {
|
||||||
|
<a href={ templ.SafeURL(next(nil, &t, playlist, cfg.AutoplayNormal, volume)) } class="btn">Switch to normal mode</a>
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
<a href={ templ.SafeURL(next(nil, &t, playlist, false, cfg.AutoplayNormal, volume)) } class="btn">Switch to normal mode</a>
|
<a href={ templ.SafeURL(t.Href() + "?playRelated=false") } class="btn">Stop playback</a>
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
<a href={ templ.SafeURL(t.Href() + "?playRelated=false") } class="btn">Stop playback</a>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user