add setting to disable waveform seekbar
This commit is contained in:
+6
-4
@@ -110,8 +110,9 @@ type NowPlayingPageConfig struct {
|
||||
}
|
||||
|
||||
type PlaybackConfig struct {
|
||||
Autoplay bool
|
||||
RepeatMode string
|
||||
Autoplay bool
|
||||
RepeatMode string
|
||||
UseWaveformSeekbar bool
|
||||
}
|
||||
|
||||
type LocalPlaybackConfig struct {
|
||||
@@ -239,8 +240,9 @@ func DefaultConfig(appVersionTag string) *Config {
|
||||
TracklistColumns: []string{"Album", "Time", "Plays"},
|
||||
},
|
||||
Playback: PlaybackConfig{
|
||||
Autoplay: false,
|
||||
RepeatMode: "None",
|
||||
Autoplay: false,
|
||||
RepeatMode: "None",
|
||||
UseWaveformSeekbar: true,
|
||||
},
|
||||
LocalPlayback: LocalPlaybackConfig{
|
||||
// "auto" is the name to pass to MPV for autoselecting the output device
|
||||
|
||||
@@ -84,6 +84,7 @@ type playbackEngine struct {
|
||||
|
||||
// to pass to onSongChange listeners; clear once listeners have been called
|
||||
lastScrobbled *mediaprovider.Track
|
||||
playbackCfg *PlaybackConfig
|
||||
scrobbleCfg *ScrobbleConfig
|
||||
transcodeCfg *TranscodingConfig
|
||||
replayGainCfg ReplayGainConfig
|
||||
@@ -118,6 +119,7 @@ func NewPlaybackEngine(
|
||||
sm: s,
|
||||
audiocache: c,
|
||||
player: p,
|
||||
playbackCfg: playbackCfg,
|
||||
scrobbleCfg: scrobbleCfg,
|
||||
transcodeCfg: transcodeCfg,
|
||||
nowPlayingIdx: -1,
|
||||
@@ -820,7 +822,11 @@ func (pm *playbackEngine) invokeNoArgCallbacks(cbs []func()) {
|
||||
func (p *playbackEngine) startPollTimePos() {
|
||||
ctx, cancel := context.WithCancel(p.ctx)
|
||||
p.cancelPollPos = cancel
|
||||
pollingTick := time.NewTicker(125 * time.Millisecond)
|
||||
pollFrequency := 250 * time.Millisecond
|
||||
if p.playbackCfg.UseWaveformSeekbar {
|
||||
pollFrequency = 100 * time.Millisecond
|
||||
}
|
||||
pollingTick := time.NewTicker(pollFrequency)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
|
||||
+82
-73
@@ -37,7 +37,10 @@ type PlaybackManager struct {
|
||||
|
||||
autoplay bool
|
||||
|
||||
lastPlayTime float64
|
||||
lastPlayTime float64
|
||||
lastPlayingID string
|
||||
wfmUpdateImageCancel context.CancelFunc
|
||||
wfmImageJobs [3]*WaveformImageJob
|
||||
}
|
||||
|
||||
type RemotePlaybackDevice struct {
|
||||
@@ -75,6 +78,22 @@ func NewPlaybackManager(
|
||||
return pm
|
||||
}
|
||||
|
||||
func (p *PlaybackManager) findWfmImageJob(id string) (*WaveformImageJob, bool) {
|
||||
for _, j := range p.wfmImageJobs {
|
||||
if j != nil && j.ItemID == id {
|
||||
return j, true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (p *PlaybackManager) addWfmImageJob(job *WaveformImageJob) {
|
||||
p.wfmImageJobs[0].Cancel()
|
||||
p.wfmImageJobs[0] = p.wfmImageJobs[1]
|
||||
p.wfmImageJobs[1] = p.wfmImageJobs[2]
|
||||
p.wfmImageJobs[2] = job
|
||||
}
|
||||
|
||||
func (p *PlaybackManager) addOnTrackChangeHook() {
|
||||
// See https://github.com/dweymouth/supersonic/issues/483
|
||||
// On Windows, MPV sometimes fails to start playback when switching to a track
|
||||
@@ -84,87 +103,21 @@ func (p *PlaybackManager) addOnTrackChangeHook() {
|
||||
p.lastPlayTime = curTime
|
||||
})
|
||||
|
||||
var waveformJobs [3]*WaveformImageJob
|
||||
findJob := func(id string) (*WaveformImageJob, bool) {
|
||||
for _, j := range waveformJobs {
|
||||
if j != nil && j.ItemID == id {
|
||||
return j, true
|
||||
p.engine.onBeforeSongChange = append(p.engine.onBeforeSongChange, func(item mediaprovider.MediaItem) {
|
||||
if p.engine.playbackCfg.UseWaveformSeekbar {
|
||||
if p.wfmGen != nil && item != nil && item.Metadata().Type == mediaprovider.MediaItemTypeTrack {
|
||||
// start generating waveform image for next-up track
|
||||
p.addWfmImageJob(p.wfmGen.StartWaveformGeneration(item.(*mediaprovider.Track)))
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
addJob := func(job *WaveformImageJob) {
|
||||
waveformJobs[0].Cancel()
|
||||
waveformJobs[0] = waveformJobs[1]
|
||||
waveformJobs[1] = waveformJobs[2]
|
||||
waveformJobs[2] = job
|
||||
}
|
||||
|
||||
p.engine.onBeforeSongChange = append(p.engine.onBeforeSongChange, func(item mediaprovider.MediaItem) {
|
||||
if p.wfmGen != nil && item != nil && item.Metadata().Type == mediaprovider.MediaItemTypeTrack {
|
||||
// start generating waveform image for next-up track
|
||||
addJob(p.wfmGen.StartWaveformGeneration(item.(*mediaprovider.Track)))
|
||||
}
|
||||
})
|
||||
|
||||
lastPlayingID := ""
|
||||
var wfmImageUpdateCancel context.CancelFunc
|
||||
|
||||
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 {
|
||||
p.enqueueAutoplayTracks()
|
||||
}
|
||||
if wfmImageUpdateCancel != nil {
|
||||
wfmImageUpdateCancel()
|
||||
}
|
||||
|
||||
updateUnfinishedJob := func(job *WaveformImageJob) {
|
||||
ctx, c := context.WithCancel(p.cache.rootCtx)
|
||||
wfmImageUpdateCancel = c
|
||||
go func(ctx context.Context, job *WaveformImageJob) {
|
||||
for {
|
||||
time.Sleep(333 * time.Millisecond)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
img := job.Get()
|
||||
for _, cb := range p.onWaveformImgUpdate {
|
||||
cb(img)
|
||||
}
|
||||
if job.Done() {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}(ctx, job)
|
||||
}
|
||||
|
||||
if item != nil {
|
||||
// cancel possible waveform generation job for previous track
|
||||
if old, ok := findJob(lastPlayingID); ok {
|
||||
old.Cancel()
|
||||
}
|
||||
lastPlayingID = item.Metadata().ID
|
||||
|
||||
var job *WaveformImageJob
|
||||
if j, ok := findJob(item.Metadata().ID); ok {
|
||||
job = j
|
||||
} else if tr, ok := item.(*mediaprovider.Track); ok {
|
||||
job = p.wfmGen.StartWaveformGeneration(tr)
|
||||
addJob(job)
|
||||
}
|
||||
if job != nil {
|
||||
img := job.Get()
|
||||
for _, cb := range p.onWaveformImgUpdate {
|
||||
cb(img)
|
||||
}
|
||||
if !job.done && job != nil {
|
||||
updateUnfinishedJob(job)
|
||||
}
|
||||
}
|
||||
}
|
||||
p.handleWaveformImageSongChange(item)
|
||||
|
||||
if runtime.GOOS != "windows" {
|
||||
return
|
||||
@@ -183,6 +136,62 @@ func (p *PlaybackManager) addOnTrackChangeHook() {
|
||||
})
|
||||
}
|
||||
|
||||
func (p *PlaybackManager) handleWaveformImageSongChange(item mediaprovider.MediaItem) {
|
||||
if p.wfmUpdateImageCancel != nil {
|
||||
p.wfmUpdateImageCancel()
|
||||
}
|
||||
if !p.engine.playbackCfg.UseWaveformSeekbar {
|
||||
return
|
||||
}
|
||||
|
||||
updateUnfinishedJob := func(job *WaveformImageJob) {
|
||||
ctx, c := context.WithCancel(p.cache.rootCtx)
|
||||
p.wfmUpdateImageCancel = c
|
||||
go func(ctx context.Context, job *WaveformImageJob) {
|
||||
for {
|
||||
time.Sleep(333 * time.Millisecond)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
img := job.Get()
|
||||
for _, cb := range p.onWaveformImgUpdate {
|
||||
cb(img)
|
||||
}
|
||||
if job.Done() {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}(ctx, job)
|
||||
}
|
||||
|
||||
if item != nil {
|
||||
// cancel possible waveform generation job for previous track
|
||||
if old, ok := p.findWfmImageJob(p.lastPlayingID); ok {
|
||||
old.Cancel()
|
||||
}
|
||||
p.lastPlayingID = item.Metadata().ID
|
||||
|
||||
var job *WaveformImageJob
|
||||
if j, ok := p.findWfmImageJob(item.Metadata().ID); ok {
|
||||
job = j
|
||||
} else if tr, ok := item.(*mediaprovider.Track); ok {
|
||||
job = p.wfmGen.StartWaveformGeneration(tr)
|
||||
p.addWfmImageJob(job)
|
||||
}
|
||||
if job != nil {
|
||||
img := job.Get()
|
||||
for _, cb := range p.onWaveformImgUpdate {
|
||||
cb(img)
|
||||
}
|
||||
if !job.done && job != nil {
|
||||
updateUnfinishedJob(job)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PlaybackManager) ScanRemotePlayers(ctx context.Context, fastScan bool) {
|
||||
if fastScan {
|
||||
p.scanRemotePlayers(ctx, 1 /*waitSec*/)
|
||||
|
||||
+2
-2
@@ -28,7 +28,7 @@ type BottomPanel struct {
|
||||
|
||||
var _ fyne.Widget = (*BottomPanel)(nil)
|
||||
|
||||
func NewBottomPanel(pm *backend.PlaybackManager, im *backend.ImageManager, contr *controller.Controller) *BottomPanel {
|
||||
func NewBottomPanel(pm *backend.PlaybackManager, im *backend.ImageManager, contr *controller.Controller, useWaveformSeekbar bool) *BottomPanel {
|
||||
bp := &BottomPanel{}
|
||||
bp.ExtendBaseWidget(bp)
|
||||
|
||||
@@ -86,7 +86,7 @@ func NewBottomPanel(pm *backend.PlaybackManager, im *backend.ImageManager, contr
|
||||
contr.ShowShareDialog(tr.ID)
|
||||
}
|
||||
}
|
||||
bp.Controls = widgets.NewPlayerControls()
|
||||
bp.Controls = widgets.NewPlayerControls(useWaveformSeekbar)
|
||||
bp.Controls.OnPlayPause(func() {
|
||||
pm.PlayPause()
|
||||
})
|
||||
|
||||
@@ -559,6 +559,12 @@ func (s *SettingsDialog) createAppearanceTab(window fyne.Window) *container.TabI
|
||||
}
|
||||
}
|
||||
|
||||
useWaveformSeekbar := widget.NewCheck(lang.L("Use waveform seekbar"), func(b bool) {
|
||||
s.config.Playback.UseWaveformSeekbar = b
|
||||
s.setRestartRequired()
|
||||
})
|
||||
useWaveformSeekbar.Checked = s.config.Playback.UseWaveformSeekbar
|
||||
|
||||
return container.NewTabItem(lang.L("Appearance"), container.NewVBox(
|
||||
util.NewHSpace(0), // insert a theme.Padding amount of space at top
|
||||
container.NewBorder(nil, nil, widget.NewLabel(lang.L("Theme")), /*left*/
|
||||
@@ -570,6 +576,8 @@ func (s *SettingsDialog) createAppearanceTab(window fyne.Window) *container.TabI
|
||||
container.NewBorder(nil, nil, widget.NewLabel(lang.L("Grid card size")), nil, gridCardSize),
|
||||
disableDPI,
|
||||
s.newSectionSeparator(),
|
||||
useWaveformSeekbar,
|
||||
s.newSectionSeparator(),
|
||||
widget.NewRichText(&widget.TextSegment{Text: lang.L("Application font"), Style: util.BoldRichTextStyle}),
|
||||
container.New(layout.NewFormLayout(),
|
||||
widget.NewLabel(lang.L("Normal font")), container.NewBorder(nil, nil, nil, normalFontBrowse, normalFontEntry),
|
||||
|
||||
+1
-1
@@ -90,7 +90,7 @@ func NewMainWindow(fyneApp fyne.App, appName, displayAppName, appVersion string,
|
||||
))
|
||||
}
|
||||
|
||||
m.BottomPanel = NewBottomPanel(app.PlaybackManager, app.ImageManager, m.Controller)
|
||||
m.BottomPanel = NewBottomPanel(app.PlaybackManager, app.ImageManager, m.Controller, m.Controller.App.Config.Playback.UseWaveformSeekbar)
|
||||
app.PlaybackManager.OnSongChange(func(item mediaprovider.MediaItem, _ *mediaprovider.Track) {
|
||||
fyne.Do(func() { m.UpdateOnTrackChange(item) })
|
||||
})
|
||||
|
||||
@@ -82,6 +82,8 @@ func (t *TrackPosSlider) IsDragging() bool {
|
||||
type PlayerControls struct {
|
||||
widget.BaseWidget
|
||||
|
||||
UseWaveformSeekbar bool
|
||||
|
||||
slider *TrackPosSlider
|
||||
waveform *WaveformSeekbar
|
||||
curTimeLabel *labelMinSize
|
||||
@@ -112,14 +114,18 @@ func NewLabelMinSize(text string, minWidth float32) *labelMinSize {
|
||||
}
|
||||
|
||||
// NewPlayerControls sets up the seek bar, and transport buttons.
|
||||
func NewPlayerControls() *PlayerControls {
|
||||
pc := &PlayerControls{}
|
||||
func NewPlayerControls(useWaveformSeekbar bool) *PlayerControls {
|
||||
pc := &PlayerControls{UseWaveformSeekbar: useWaveformSeekbar}
|
||||
pc.ExtendBaseWidget(pc)
|
||||
|
||||
pc.slider = NewTrackPosSlider()
|
||||
pc.slider.Disable()
|
||||
pc.slider.Hide()
|
||||
pc.waveform = NewWaveformSeekbar()
|
||||
if useWaveformSeekbar {
|
||||
pc.slider.Hidden = true
|
||||
} else {
|
||||
pc.waveform.Hidden = true
|
||||
}
|
||||
pc.curTimeLabel = NewLabelMinSize(util.SecondsToMMSS(0), 55)
|
||||
pc.curTimeLabel.Alignment = fyne.TextAlignTrailing
|
||||
pc.totalTimeLabel = NewLabelMinSize(util.SecondsToMMSS(0), 55)
|
||||
@@ -221,6 +227,12 @@ func (p *PlayerControls) UpdateWaveformImg(img *backend.WaveformImage) {
|
||||
p.waveform.UpdateImage(img)
|
||||
}
|
||||
|
||||
func (p *PlayerControls) Refresh() {
|
||||
p.waveform.Hidden = !p.UseWaveformSeekbar
|
||||
p.slider.Hidden = p.UseWaveformSeekbar
|
||||
p.BaseWidget.Refresh()
|
||||
}
|
||||
|
||||
func (p *PlayerControls) CreateRenderer() fyne.WidgetRenderer {
|
||||
return widget.NewSimpleRenderer(p.container)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user