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