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 {
|
type PlaybackConfig struct {
|
||||||
Autoplay bool
|
Autoplay bool
|
||||||
RepeatMode string
|
RepeatMode string
|
||||||
UseWaveformSeekbar bool
|
SkipOneStarWhenShuffling bool
|
||||||
|
SkipKeywordWhenShuffling string
|
||||||
|
UseWaveformSeekbar bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type LocalPlaybackConfig struct {
|
type LocalPlaybackConfig struct {
|
||||||
|
|||||||
+32
-18
@@ -11,6 +11,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/charlievieth/strcase"
|
||||||
"github.com/dweymouth/supersonic/backend/mediaprovider"
|
"github.com/dweymouth/supersonic/backend/mediaprovider"
|
||||||
"github.com/dweymouth/supersonic/backend/player"
|
"github.com/dweymouth/supersonic/backend/player"
|
||||||
"github.com/dweymouth/supersonic/backend/player/dlna"
|
"github.com/dweymouth/supersonic/backend/player/dlna"
|
||||||
@@ -27,7 +28,8 @@ type PlaybackManager struct {
|
|||||||
wfmGen *WaveformImageGenerator
|
wfmGen *WaveformImageGenerator
|
||||||
cache *AudioCache
|
cache *AudioCache
|
||||||
cmdQueue *playbackCommandQueue
|
cmdQueue *playbackCommandQueue
|
||||||
cfg *AppConfig
|
appCfg *AppConfig
|
||||||
|
cfg *PlaybackConfig
|
||||||
|
|
||||||
localPlayer player.BasePlayer
|
localPlayer player.BasePlayer
|
||||||
remotePlayersLock sync.Mutex
|
remotePlayersLock sync.Mutex
|
||||||
@@ -36,8 +38,6 @@ type PlaybackManager struct {
|
|||||||
|
|
||||||
onWaveformImgUpdate []func(*WaveformImage)
|
onWaveformImgUpdate []func(*WaveformImage)
|
||||||
|
|
||||||
autoplay bool
|
|
||||||
|
|
||||||
lastPlayTime float64
|
lastPlayTime float64
|
||||||
lastPlayingID string
|
lastPlayingID string
|
||||||
wfmUpdateImageCancel context.CancelFunc
|
wfmUpdateImageCancel context.CancelFunc
|
||||||
@@ -66,8 +66,8 @@ func NewPlaybackManager(
|
|||||||
pm := &PlaybackManager{
|
pm := &PlaybackManager{
|
||||||
engine: e,
|
engine: e,
|
||||||
cmdQueue: q,
|
cmdQueue: q,
|
||||||
cfg: appCfg,
|
appCfg: appCfg,
|
||||||
autoplay: playbackCfg.Autoplay,
|
cfg: playbackCfg,
|
||||||
localPlayer: p,
|
localPlayer: p,
|
||||||
cache: c,
|
cache: c,
|
||||||
}
|
}
|
||||||
@@ -117,7 +117,7 @@ func (p *PlaybackManager) addOnTrackChangeHook() {
|
|||||||
|
|
||||||
p.OnSongChange(func(item mediaprovider.MediaItem, _ *mediaprovider.Track) {
|
p.OnSongChange(func(item mediaprovider.MediaItem, _ *mediaprovider.Track) {
|
||||||
// Autoplay if enabled and we are on the last 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.enqueueAutoplayTracks()
|
||||||
}
|
}
|
||||||
p.handleWaveformImageSongChange(item)
|
p.handleWaveformImageSongChange(item)
|
||||||
@@ -470,13 +470,23 @@ func (p *PlaybackManager) PlayTrackAt(idx int) {
|
|||||||
|
|
||||||
func (p *PlaybackManager) PlayRandomSongs(genreName string) error {
|
func (p *PlaybackManager) PlayRandomSongs(genreName string) error {
|
||||||
return p.fetchAndPlayTracks(func() ([]*mediaprovider.Track, 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 {
|
func (p *PlaybackManager) PlaySimilarSongs(id string) error {
|
||||||
return p.fetchAndPlayTracks(func() ([]*mediaprovider.Track, 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 {
|
func (p *PlaybackManager) IsAutoplay() bool {
|
||||||
return p.autoplay
|
return p.cfg.Autoplay
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PlaybackManager) PlaybackStatus() PlaybackStatus {
|
func (p *PlaybackManager) PlaybackStatus() PlaybackStatus {
|
||||||
@@ -604,7 +614,7 @@ func (p *PlaybackManager) SetVolume(vol int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *PlaybackManager) SetAutoplay(autoplay bool) {
|
func (p *PlaybackManager) SetAutoplay(autoplay bool) {
|
||||||
p.autoplay = autoplay
|
p.cfg.Autoplay = autoplay
|
||||||
if autoplay && p.NowPlayingIndex() == len(p.engine.playQueue)-1 {
|
if autoplay && p.NowPlayingIndex() == len(p.engine.playQueue)-1 {
|
||||||
p.enqueueAutoplayTracks()
|
p.enqueueAutoplayTracks()
|
||||||
}
|
}
|
||||||
@@ -705,11 +715,15 @@ func (p *PlaybackManager) enqueueAutoplayTracks() {
|
|||||||
// tracks we will enqueue
|
// tracks we will enqueue
|
||||||
var tracks []*mediaprovider.Track
|
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 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 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
|
// similar tracks by artist
|
||||||
if len(tr.ArtistIDs) > 0 {
|
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 {
|
if err != nil {
|
||||||
log.Printf("autoplay error: failed to get similar tracks: %v", err)
|
log.Printf("autoplay error: failed to get similar tracks: %v", err)
|
||||||
}
|
}
|
||||||
tracks = filterRecentlyPlayed(similar)
|
tracks = filterAutoplayTracks(similar)
|
||||||
}
|
}
|
||||||
|
|
||||||
// fallback to random tracks from genre
|
// fallback to random tracks from genre
|
||||||
@@ -735,11 +749,11 @@ func (p *PlaybackManager) enqueueAutoplayTracks() {
|
|||||||
if g == "" {
|
if g == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
byGenre, err := s.GetRandomTracks(g, p.cfg.EnqueueBatchSize)
|
byGenre, err := s.GetRandomTracks(g, p.appCfg.EnqueueBatchSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("autoplay error: failed to get tracks by genre: %v", err)
|
log.Printf("autoplay error: failed to get tracks by genre: %v", err)
|
||||||
}
|
}
|
||||||
tracks = filterRecentlyPlayed(byGenre)
|
tracks = filterAutoplayTracks(byGenre)
|
||||||
if len(tracks) > 0 {
|
if len(tracks) > 0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -750,11 +764,11 @@ func (p *PlaybackManager) enqueueAutoplayTracks() {
|
|||||||
// random tracks works regardless of the type of the last playing media
|
// random tracks works regardless of the type of the last playing media
|
||||||
if len(tracks) == 0 {
|
if len(tracks) == 0 {
|
||||||
// fallback to random tracks
|
// fallback to random tracks
|
||||||
random, err := s.GetRandomTracks("", p.cfg.EnqueueBatchSize)
|
random, err := s.GetRandomTracks("", p.appCfg.EnqueueBatchSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("autoplay error: failed to get random tracks: %v", err)
|
log.Printf("autoplay error: failed to get random tracks: %v", err)
|
||||||
}
|
}
|
||||||
tracks = filterRecentlyPlayed(random)
|
tracks = filterAutoplayTracks(random)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(tracks) > 0 {
|
if len(tracks) > 0 {
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ require (
|
|||||||
al.essio.dev/pkg/shellescape v1.5.1 // indirect
|
al.essio.dev/pkg/shellescape v1.5.1 // indirect
|
||||||
fyne.io/systray v1.11.0 // indirect
|
fyne.io/systray v1.11.0 // indirect
|
||||||
github.com/BurntSushi/toml v1.4.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/danieljoos/wincred v1.2.2 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/fogleman/gg v1.3.0 // 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/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 h1:Pt0vfRZ8enkZh1n22RvoboA53SMM/v2aEwNQTZKSqww=
|
||||||
github.com/cenkalti/dominantcolor v1.0.3/go.mod h1:mGpFMbWUnyXaGN48Zbf9bU9HJP1eCCD7dnsscb4lyR4=
|
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 h1:774zMFJrqaeYCK2W57BgAem/MLi6mtSE47MB6BOJ0i0=
|
||||||
github.com/danieljoos/wincred v1.2.2/go.mod h1:w7w4Utbrz8lqeMbDAK0lkNJUv5sAOkFi7nd/ogr0Uh8=
|
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=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
|||||||
@@ -211,8 +211,10 @@
|
|||||||
"Singles": "Singles",
|
"Singles": "Singles",
|
||||||
"Size": "Size",
|
"Size": "Size",
|
||||||
"Skip duplicate tracks": "Skip duplicate tracks",
|
"Skip duplicate tracks": "Skip duplicate tracks",
|
||||||
|
"Skip one-star tracks": "Skip one-star tracks",
|
||||||
"Skip SSL certificate verification": "Skip SSL certificate verification",
|
"Skip SSL certificate verification": "Skip SSL certificate verification",
|
||||||
"Skip this version": "Skip this version",
|
"Skip this version": "Skip this version",
|
||||||
|
"Skip tracks with keyword": "Skip tracks with keyword",
|
||||||
"Smaller": "Smaller",
|
"Smaller": "Smaller",
|
||||||
"Sort": "Sort",
|
"Sort": "Sort",
|
||||||
"Soundtrack": "Soundtrack",
|
"Soundtrack": "Soundtrack",
|
||||||
@@ -251,6 +253,7 @@
|
|||||||
"version": "version",
|
"version": "version",
|
||||||
"Visualizations": "Visualizations",
|
"Visualizations": "Visualizations",
|
||||||
"Volume": "Volume",
|
"Volume": "Volume",
|
||||||
|
"When enqueuing random": "When enqueuing random",
|
||||||
"wrong URL": "wrong URL",
|
"wrong URL": "wrong URL",
|
||||||
"wrong username/password": "wrong username/password",
|
"wrong username/password": "wrong username/password",
|
||||||
"Year": "Year",
|
"Year": "Year",
|
||||||
|
|||||||
@@ -427,12 +427,19 @@ func (s *SettingsDialog) createPlaybackTab(isLocalPlayer, isReplayGainPlayer boo
|
|||||||
disableTranscode,
|
disableTranscode,
|
||||||
container.NewHBox(transcode, transcodeCodec, transcodeBitRate),
|
container.NewHBox(transcode, transcodeCodec, transcodeBitRate),
|
||||||
s.newSectionSeparator(),
|
s.newSectionSeparator(),
|
||||||
widget.NewRichText(&widget.TextSegment{Text: "ReplayGain", Style: util.BoldRichTextStyle}),
|
widget.NewLabelWithStyle("ReplayGain", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
|
||||||
container.New(layout.NewFormLayout(),
|
container.New(layout.NewFormLayout(),
|
||||||
widget.NewLabel(lang.L("ReplayGain mode")), container.NewGridWithColumns(2, replayGainSelect),
|
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("ReplayGain preamp")), container.NewHBox(preampGain, widget.NewLabel("dB")),
|
||||||
widget.NewLabel(lang.L("Prevent clipping")), preventClipping,
|
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