Skip to content

feat: sleep timer#228

Open
brrock wants to merge 3 commits intohummingbird-player:masterfrom
brrock:master
Open

feat: sleep timer#228
brrock wants to merge 3 commits intohummingbird-player:masterfrom
brrock:master

Conversation

@brrock
Copy link
Copy Markdown

@brrock brrock commented Mar 25, 2026

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)

brrock and others added 2 commits March 25, 2026 07:31
- 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]>
@brrock brrock changed the title fix: tighten sleep timer button and popover layout feat: sleep timer Mar 25, 2026
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) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This should be a button().

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));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This takes effect immediately but the stop command might not. On Windows in my testing this produces a very noticeable instantaneous volume spike.

@brrock
Copy link
Copy Markdown
Author

brrock commented Mar 25, 2026

Much appreciate the review, will address, thanks

@brrock
Copy link
Copy Markdown
Author

brrock commented Mar 25, 2026

I'm sorry about slop PR, will improve

@transitoryangel
Copy link
Copy Markdown
Collaborator

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Sleep timer

3 participants