add settings for skipping one-star or tracks with keyword (#724)
This commit is contained in:
+5
-3
@@ -112,9 +112,11 @@ type NowPlayingPageConfig struct {
|
||||
}
|
||||
|
||||
type PlaybackConfig struct {
|
||||
Autoplay bool
|
||||
RepeatMode string
|
||||
UseWaveformSeekbar bool
|
||||
Autoplay bool
|
||||
RepeatMode string
|
||||
SkipOneStarWhenShuffling bool
|
||||
SkipKeywordWhenShuffling string
|
||||
UseWaveformSeekbar bool
|
||||
}
|
||||
|
||||
type LocalPlaybackConfig struct {
|
||||
|
||||
+32
-18
@@ -11,6 +11,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/charlievieth/strcase"
|
||||
"github.com/dweymouth/supersonic/backend/mediaprovider"
|
||||
"github.com/dweymouth/supersonic/backend/player"
|
||||
"github.com/dweymouth/supersonic/backend/player/dlna"
|
||||
@@ -27,7 +28,8 @@ type PlaybackManager struct {
|
||||
wfmGen *WaveformImageGenerator
|
||||
cache *AudioCache
|
||||
cmdQueue *playbackCommandQueue
|
||||
cfg *AppConfig
|
||||
appCfg *AppConfig
|
||||
cfg *PlaybackConfig
|
||||
|
||||
localPlayer player.BasePlayer
|
||||
remotePlayersLock sync.Mutex
|
||||
@@ -36,8 +38,6 @@ type PlaybackManager struct {
|
||||
|
||||
onWaveformImgUpdate []func(*WaveformImage)
|
||||
|
||||
autoplay bool
|
||||
|
||||
lastPlayTime float64
|
||||
lastPlayingID string
|
||||
wfmUpdateImageCancel context.CancelFunc
|
||||
@@ -66,8 +66,8 @@ func NewPlaybackManager(
|
||||
pm := &PlaybackManager{
|
||||
engine: e,
|
||||
cmdQueue: q,
|
||||
cfg: appCfg,
|
||||
autoplay: playbackCfg.Autoplay,
|
||||
appCfg: appCfg,
|
||||
cfg: playbackCfg,
|
||||
localPlayer: p,
|
||||
cache: c,
|
||||
}
|
||||
@@ -117,7 +117,7 @@ func (p *PlaybackManager) addOnTrackChangeHook() {
|
||||
|
||||
p.OnSongChange(func(item mediaprovider.MediaItem, _ *mediaprovider.Track) {
|
||||
// Autoplay if enabled and we are on the last track
|
||||
if p.autoplay && p.NowPlayingIndex() == len(p.engine.playQueue)-1 {
|
||||
if p.cfg.Autoplay && p.NowPlayingIndex() == len(p.engine.playQueue)-1 {
|
||||
p.enqueueAutoplayTracks()
|
||||
}
|
||||
p.handleWaveformImageSongChange(item)
|
||||
@@ -470,13 +470,23 @@ func (p *PlaybackManager) PlayTrackAt(idx int) {
|
||||
|
||||
func (p *PlaybackManager) PlayRandomSongs(genreName string) error {
|
||||
return p.fetchAndPlayTracks(func() ([]*mediaprovider.Track, error) {
|
||||
return p.engine.sm.Server.GetRandomTracks(genreName, p.cfg.EnqueueBatchSize)
|
||||
tr, err := p.engine.sm.Server.GetRandomTracks(genreName, p.appCfg.EnqueueBatchSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sharedutil.FilterSlice(tr, func(t *mediaprovider.Track) bool {
|
||||
skipKwd := p.cfg.SkipKeywordWhenShuffling
|
||||
include :=
|
||||
(skipKwd == "" || !strcase.Contains(t.Title, skipKwd)) &&
|
||||
(!p.cfg.SkipOneStarWhenShuffling || t.Rating != 1)
|
||||
return include
|
||||
}), nil
|
||||
})
|
||||
}
|
||||
|
||||
func (p *PlaybackManager) PlaySimilarSongs(id string) error {
|
||||
return p.fetchAndPlayTracks(func() ([]*mediaprovider.Track, error) {
|
||||
return p.engine.sm.Server.GetSimilarTracks(id, p.cfg.EnqueueBatchSize)
|
||||
return p.engine.sm.Server.GetSimilarTracks(id, p.appCfg.EnqueueBatchSize)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -592,7 +602,7 @@ func (p *PlaybackManager) GetLoopMode() LoopMode {
|
||||
}
|
||||
|
||||
func (p *PlaybackManager) IsAutoplay() bool {
|
||||
return p.autoplay
|
||||
return p.cfg.Autoplay
|
||||
}
|
||||
|
||||
func (p *PlaybackManager) PlaybackStatus() PlaybackStatus {
|
||||
@@ -604,7 +614,7 @@ func (p *PlaybackManager) SetVolume(vol int) {
|
||||
}
|
||||
|
||||
func (p *PlaybackManager) SetAutoplay(autoplay bool) {
|
||||
p.autoplay = autoplay
|
||||
p.cfg.Autoplay = autoplay
|
||||
if autoplay && p.NowPlayingIndex() == len(p.engine.playQueue)-1 {
|
||||
p.enqueueAutoplayTracks()
|
||||
}
|
||||
@@ -705,11 +715,15 @@ func (p *PlaybackManager) enqueueAutoplayTracks() {
|
||||
// tracks we will enqueue
|
||||
var tracks []*mediaprovider.Track
|
||||
|
||||
filterRecentlyPlayed := func(tracks []*mediaprovider.Track) []*mediaprovider.Track {
|
||||
filterAutoplayTracks := func(tracks []*mediaprovider.Track) []*mediaprovider.Track {
|
||||
return sharedutil.FilterSlice(tracks, func(t *mediaprovider.Track) bool {
|
||||
return !slices.ContainsFunc(queue, func(i mediaprovider.MediaItem) bool {
|
||||
shouldSkip :=
|
||||
(p.cfg.SkipOneStarWhenShuffling && t.Rating == 1) ||
|
||||
(p.cfg.SkipKeywordWhenShuffling != "" && strcase.Contains(t.Title, p.cfg.SkipKeywordWhenShuffling))
|
||||
recentlyPlayed := slices.ContainsFunc(queue, func(i mediaprovider.MediaItem) bool {
|
||||
return i.Metadata().Type == mediaprovider.MediaItemTypeTrack && i.Metadata().ID == t.ID
|
||||
})
|
||||
return !shouldSkip && !recentlyPlayed
|
||||
})
|
||||
}
|
||||
|
||||
@@ -722,11 +736,11 @@ func (p *PlaybackManager) enqueueAutoplayTracks() {
|
||||
|
||||
// similar tracks by artist
|
||||
if len(tr.ArtistIDs) > 0 {
|
||||
similar, err := s.GetSimilarTracks(tr.ArtistIDs[0], p.cfg.EnqueueBatchSize)
|
||||
similar, err := s.GetSimilarTracks(tr.ArtistIDs[0], p.appCfg.EnqueueBatchSize)
|
||||
if err != nil {
|
||||
log.Printf("autoplay error: failed to get similar tracks: %v", err)
|
||||
}
|
||||
tracks = filterRecentlyPlayed(similar)
|
||||
tracks = filterAutoplayTracks(similar)
|
||||
}
|
||||
|
||||
// fallback to random tracks from genre
|
||||
@@ -735,11 +749,11 @@ func (p *PlaybackManager) enqueueAutoplayTracks() {
|
||||
if g == "" {
|
||||
continue
|
||||
}
|
||||
byGenre, err := s.GetRandomTracks(g, p.cfg.EnqueueBatchSize)
|
||||
byGenre, err := s.GetRandomTracks(g, p.appCfg.EnqueueBatchSize)
|
||||
if err != nil {
|
||||
log.Printf("autoplay error: failed to get tracks by genre: %v", err)
|
||||
}
|
||||
tracks = filterRecentlyPlayed(byGenre)
|
||||
tracks = filterAutoplayTracks(byGenre)
|
||||
if len(tracks) > 0 {
|
||||
break
|
||||
}
|
||||
@@ -750,11 +764,11 @@ func (p *PlaybackManager) enqueueAutoplayTracks() {
|
||||
// random tracks works regardless of the type of the last playing media
|
||||
if len(tracks) == 0 {
|
||||
// fallback to random tracks
|
||||
random, err := s.GetRandomTracks("", p.cfg.EnqueueBatchSize)
|
||||
random, err := s.GetRandomTracks("", p.appCfg.EnqueueBatchSize)
|
||||
if err != nil {
|
||||
log.Printf("autoplay error: failed to get random tracks: %v", err)
|
||||
}
|
||||
tracks = filterRecentlyPlayed(random)
|
||||
tracks = filterAutoplayTracks(random)
|
||||
}
|
||||
|
||||
if len(tracks) > 0 {
|
||||
|
||||
@@ -33,6 +33,7 @@ require (
|
||||
al.essio.dev/pkg/shellescape v1.5.1 // indirect
|
||||
fyne.io/systray v1.11.0 // indirect
|
||||
github.com/BurntSushi/toml v1.4.0 // indirect
|
||||
github.com/charlievieth/strcase v0.0.5 // indirect
|
||||
github.com/danieljoos/wincred v1.2.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/fogleman/gg v1.3.0 // indirect
|
||||
|
||||
@@ -10,6 +10,8 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/cenkalti/dominantcolor v1.0.3 h1:Pt0vfRZ8enkZh1n22RvoboA53SMM/v2aEwNQTZKSqww=
|
||||
github.com/cenkalti/dominantcolor v1.0.3/go.mod h1:mGpFMbWUnyXaGN48Zbf9bU9HJP1eCCD7dnsscb4lyR4=
|
||||
github.com/charlievieth/strcase v0.0.5 h1:gV4iXVyD6eI5KdfOV+/vIVCKXZwtCWOmDMcu7Uy00Rs=
|
||||
github.com/charlievieth/strcase v0.0.5/go.mod h1:FIOYY1aDBMSIOFqmVomHBpoK+bteGlESRsgsdWjrhx8=
|
||||
github.com/danieljoos/wincred v1.2.2 h1:774zMFJrqaeYCK2W57BgAem/MLi6mtSE47MB6BOJ0i0=
|
||||
github.com/danieljoos/wincred v1.2.2/go.mod h1:w7w4Utbrz8lqeMbDAK0lkNJUv5sAOkFi7nd/ogr0Uh8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
|
||||
@@ -211,8 +211,10 @@
|
||||
"Singles": "Singles",
|
||||
"Size": "Size",
|
||||
"Skip duplicate tracks": "Skip duplicate tracks",
|
||||
"Skip one-star tracks": "Skip one-star tracks",
|
||||
"Skip SSL certificate verification": "Skip SSL certificate verification",
|
||||
"Skip this version": "Skip this version",
|
||||
"Skip tracks with keyword": "Skip tracks with keyword",
|
||||
"Smaller": "Smaller",
|
||||
"Sort": "Sort",
|
||||
"Soundtrack": "Soundtrack",
|
||||
@@ -251,6 +253,7 @@
|
||||
"version": "version",
|
||||
"Visualizations": "Visualizations",
|
||||
"Volume": "Volume",
|
||||
"When enqueuing random": "When enqueuing random",
|
||||
"wrong URL": "wrong URL",
|
||||
"wrong username/password": "wrong username/password",
|
||||
"Year": "Year",
|
||||
|
||||
@@ -427,12 +427,19 @@ func (s *SettingsDialog) createPlaybackTab(isLocalPlayer, isReplayGainPlayer boo
|
||||
disableTranscode,
|
||||
container.NewHBox(transcode, transcodeCodec, transcodeBitRate),
|
||||
s.newSectionSeparator(),
|
||||
widget.NewRichText(&widget.TextSegment{Text: "ReplayGain", Style: util.BoldRichTextStyle}),
|
||||
widget.NewLabelWithStyle("ReplayGain", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
|
||||
container.New(layout.NewFormLayout(),
|
||||
widget.NewLabel(lang.L("ReplayGain mode")), container.NewGridWithColumns(2, replayGainSelect),
|
||||
widget.NewLabel(lang.L("ReplayGain preamp")), container.NewHBox(preampGain, widget.NewLabel("dB")),
|
||||
widget.NewLabel(lang.L("Prevent clipping")), preventClipping,
|
||||
),
|
||||
s.newSectionSeparator(),
|
||||
widget.NewLabelWithStyle(lang.L("When enqueuing random"), fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
|
||||
widget.NewCheckWithData(lang.L("Skip one-star tracks"), binding.BindBool(&s.config.Playback.SkipOneStarWhenShuffling)),
|
||||
container.NewBorder(nil, nil,
|
||||
widget.NewLabel(lang.L("Skip tracks with keyword")), nil,
|
||||
widget.NewEntryWithData(binding.BindString(&s.config.Playback.SkipKeywordWhenShuffling)),
|
||||
),
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user