Conversation
- Add SetSleepTimer(u64) / CancelSleepTimer commands and SleepTimerUpdated(Option<u64>) event to the playback event system - Track sleep timer deadline and pre-fade volume in PlaybackThread; check timer each main_loop iteration — fades volume linearly over the final 10 seconds then stops and restores the original volume level - Expose set_sleep_timer() / cancel_sleep_timer() on PlaybackInterface; handle SleepTimerUpdated in the broadcast loop to keep PlaybackInfo up to date - Add sleep_timer_remaining: Entity<Option<u64>> to PlaybackInfo and initialise it in build_models() - Add moon.svg icon (Tabler Icons style) and MOON constant - New SleepTimer GPUI component: moon-icon button shows a live MM:SS (or H:MM:SS) countdown when active; opens a TopCenter popover with preset buttons (15m / 30m / 45m / 1h / 1h30 / 2h) and a Cancel link when a timer is running - Embed SleepTimer in SecondaryControls between the ReplayGain button and the queue/lyrics separator Co-authored-by: Copilot <[email protected]>
- Fix the timer button width so the idle state stays compact - Clip overflow when the countdown is shown - Move the popover to the top-right and reduce its minimum width Co-authored-by: Copilot <[email protected]>
| let faded_vol = pre_fade_vol * fade_factor; | ||
| // Apply directly to engine (without updating current_volume) so cancel can | ||
| // restore the correct original level. | ||
| if let Err(e) = self.engine.set_volume(faded_vol) { |
There was a problem hiding this comment.
This fade implementation is questionable - it isn't applied per-sample, it's applied per-packet, which can be more than 4096 samples (~1/10th of a second at 44.1). Not sure how much of a problem this realistically is but it's definitely not "correct".
You don't have to fix this if you don't want to. I'll add a more correct implementation of fading to fix #153, and when that's done I'll change this to use that.
| ) | ||
| // Preset buttons grid | ||
| .child( | ||
| div().flex().flex_row().flex_wrap().gap(px(6.0)).children( |
There was a problem hiding this comment.
This UI is pretty bad. I would prefer this to use segmented_control (like the ReplayGain popover does).
Additionally, the user should be able to set custom values here and the default options should only go up to 1h (so that the options list can be fit in one row)
| ) | ||
| // Cancel button (only shown when a timer is active) | ||
| .when(timer_active, |this| { | ||
| this.child( |
| if let Err(e) = self.engine.set_volume(faded_vol) { | ||
| warn!("Sleep timer fade: failed to set volume: {:?}", e); | ||
| } else { | ||
| self.send_event(PlaybackEvent::VolumeChanged(faded_vol)); |
There was a problem hiding this comment.
This should not send VolumeChanged events.
| self.sleep_timer_deadline = None; | ||
| self.last_timer_broadcast_secs = None; | ||
| if let Some(vol) = self.sleep_timer_pre_fade_volume.take() { | ||
| self.set_volume(vol); |
There was a problem hiding this comment.
This takes effect immediately but the stop command might not. On Windows in my testing this produces a very noticeable instantaneous volume spike.
|
Much appreciate the review, will address, thanks |
|
I'm sorry about slop PR, will improve |
|
Since this PR was made, the generative AI contribution guidelines have changed (see here). Wanted to give a heads up that this PR will be closed if it is not changed to meet the new requirements by April 13th. |
Added sleep timer, fixes #42, made by copilot, tested well. (here's a more detailed description)
Playback engine (src/playback/)
New SetSleepTimer(u64) and CancelSleepTimer commands, plus a SleepTimerUpdated(Option) broadcast event
PlaybackThread tracks a deadline (Instant), a pre-fade volume anchor, and the last-broadcast remaining seconds
Each main_loop iteration calls check_sleep_timer — during the final 10 seconds it linearly fades volume to zero, then stops playback and restores the original volume level
Cancelling mid-fade also restores the saved volume
PlaybackInterface exposes set_sleep_timer(secs) and cancel_sleep_timer(); the broadcast loop forwards SleepTimerUpdated into PlaybackInfo
UI (src/ui/)
sleep_timer_remaining: Entity<Option> added to PlaybackInfo
New SleepTimer GPUI component: a compact moon-icon button that shows a live MM:SS / H:MM:SS countdown when active; opens a popover with six preset durations (15 m – 2 h) and a cancel option
Button is a fixed 25 px when idle and 62 px when the countdown is visible, with overflow clipping so it can't push the control bar out of bounds
Popover uses TopRight anchoring so it stays within the window when the button is near the right edge
Component is placed in SecondaryControls between the ReplayGain button and the queue/lyrics separator
New moon.svg icon (Tabler Icons style)