Skip to content

Add playback speed control (0.25x - 4x) with UI selector#766

Open
localhost5173 wants to merge 2 commits intodweymouth:mainfrom
localhost5173:main
Open

Add playback speed control (0.25x - 4x) with UI selector#766
localhost5173 wants to merge 2 commits intodweymouth:mainfrom
localhost5173:main

Conversation

@localhost5173
Copy link
Copy Markdown

This PR adds playback speed control functionality to Supersonic, allowing users to adjust playback speed from 0.25x to 4.0x in 0.25x increments.

SetVolume(int) error
GetVolume() int

SetSpeed(float64) error
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than having these on the BasePlayer interface I think it makes sense to have a SpeedPlayer interface that has just these two functions. Only the MPV player would implement it, and then the UI code (either in bottompanel.go or maybe mainwindow.go) can subscribe to the PlaybackManager.OnPlayerChange hook to interface-assert against the SpeedPlayer interface and disable the UI button if the current player does not support changing speed.

return p.engine.CurrentPlayer().GetVolume()
}

func (p *PlaybackManager) SetSpeed(speed float64) {
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you be able to look into implementing the MPRIS support for this as well? There are Rate and SetRate stubs on the MPRIS handler here - https://github.com/dweymouth/supersonic/blob/main/backend/mpris.go#L242. Exporting Rate properly via MPRIS will keep the OS's concept of the playback position in sync with the actual position.

It would also be good to implement the same for Windows and Mac, though if you prefer, I can merge this PR to a feature branch once it's ready and then finish that myself. If nothing else, it can just give the OS an updated playback position once every 5 or 10 seconds to keep it from getting too out-of-sync.

}

func (a *AuxControls) showSpeedMenu() {
speeds := []float64{0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0, 2.25, 2.5, 2.75, 3.0, 3.25, 3.5, 3.75, 4.0}
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer instead of a huge menu like this if the menu had fewer options (e.g. 0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2, 4) plus a "Custom" option that would launch a PopUp with a slider to set a custom speed outside these options. That is something else I can do in a follow-up if you prefer.

Copy link
Copy Markdown

@greg-freewave greg-freewave Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW i'm following this issue as I use speed changes for many reasons, speedup slow podcasts, slow down music for learning on guitar, changing the pitch to see if it blends with other songs etc. For a non dj music app i think Symphonium has a nice interface that strikes a balance between useful presets and customization:
1000010843

Not trying to derail or influence really, just provide a nice example interface from another personal streaming app I use. ❤️

Copilot AI review requested due to automatic review settings February 16, 2026 07:45
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds playback speed control to Supersonic (0.25x–4.0x) by wiring a new UI selector through PlaybackManager down into the underlying player implementations.

Changes:

  • Add a new playback-speed selector button/menu to the bottom “aux controls” UI.
  • Extend the backend player interface + playback command queue/engine/manager to support setting/getting speed.
  • Implement speed support for the MPV player, and stub implementations for DLNA/Jukebox players.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
ui/widgets/auxcontrols.go Adds a speed button and popup menu to select playback speed; highlights when non-1.0x.
ui/bottompanel.go Wires AuxControls speed changes into PlaybackManager and initializes the speed indicator.
backend/player/player.go Extends BasePlayer interface with SetSpeed/GetSpeed.
backend/player/mpv/player.go Implements MPV speed set/get via the speed property.
backend/player/jukebox/jukeboxplayer.go Adds no-op speed methods for jukebox mode.
backend/player/dlna/dlnaplayer.go Adds no-op speed methods for DLNA mode.
backend/playbackmanager.go Adds SetSpeed/Speed and handles cmdSpeed in the command loop.
backend/playbackengine.go Adds speed clamping + forwards to current player.
backend/playbackcommands.go Adds a cmdSpeed command and queue method for coalescing speed changes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +125 to +129
bp.AuxControls.OnChangeSpeed = func(speed float64) {
pm.SetSpeed(speed)
}
// Set initial speed from playback manager
bp.AuxControls.SetSpeed(pm.Speed())
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AuxControls speed state is only initialized once via SetSpeed(pm.Speed()). If the user switches between local/remote players (pm.OnPlayerChange), the speed indicator can become stale (e.g., remain highlighted when casting to a player that always reports 1.0x). Consider updating the existing OnPlayerChange handler to also sync bp.AuxControls.SetSpeed(pm.Speed()) (and/or disable the speed control when not supported).

Copilot uses AI. Check for mistakes.
Comment on lines +44 to +48
// SetSpeed is not supported for Jukebox players, returns nil (no-op).
func (j *JukeboxPlayer) SetSpeed(speed float64) error {
// Jukebox mode does not support playback speed control
return nil
}
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SetSpeed() is implemented as a silent no-op returning nil, but GetSpeed() always returns 1.0. That makes it impossible for callers to distinguish “speed set successfully” from “unsupported”, and can lead the UI to show a non-1.0x state even though playback speed can’t change. Prefer returning a non-nil error for unsupported speed control (or otherwise reflect the requested speed consistently).

Copilot uses AI. Check for mistakes.
Comment on lines +144 to +147
// SetSpeed is not supported for DLNA players, returns nil (no-op).
func (d *DLNAPlayer) SetSpeed(speed float64) error {
// DLNA/UPnP does not typically support playback speed control
return nil
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SetSpeed() is a silent no-op returning nil while GetSpeed() always returns 1.0. This makes the operation appear successful to callers even though playback speed cannot change, which can desync UI state. Prefer returning an explicit error for unsupported speed control (or otherwise expose support/actual speed to callers).

Suggested change
// SetSpeed is not supported for DLNA players, returns nil (no-op).
func (d *DLNAPlayer) SetSpeed(speed float64) error {
// DLNA/UPnP does not typically support playback speed control
return nil
// SetSpeed is not supported for DLNA players; return an explicit error instead of silently no-op'ing.
func (d *DLNAPlayer) SetSpeed(speed float64) error {
if d.destroyed {
// Maintain existing behavior for destroyed players.
return nil
}
// DLNA/UPnP does not typically support playback speed control.
return errors.New("DLNA playback speed control is not supported")

Copilot uses AI. Check for mistakes.
}

a.speed.IconSize = IconButtonSizeSmaller
a.speed.SetToolTip("Playback speed")
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New UI strings for the speed control aren’t localized (e.g., tooltip text). The rest of this widget uses lang.L(...) for user-visible strings, so these should also go through the localization layer for consistency and translation support.

Suggested change
a.speed.SetToolTip("Playback speed")
a.speed.SetToolTip(lang.L("Playback speed"))

Copilot uses AI. Check for mistakes.
Comment on lines +145 to +147
label := fmt.Sprintf("%.2fx", s)
if s == 1.0 {
label = "1x (Normal)"
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The speed menu item label "1x (Normal)" (and the general formatting of speed labels) is hard-coded English text. Please route these labels through lang.L(...) (and format using the localized template) so translations can cover the speed selector.

Suggested change
label := fmt.Sprintf("%.2fx", s)
if s == 1.0 {
label = "1x (Normal)"
label := lang.L("%.2fx", s)
if s == 1.0 {
label = lang.L("1x (Normal)")

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants