Skip to main content

#
jellytau_lib/player/
sleep_timer.rs

    1use serde::{Deserialize, Serialize};
    2
    3/// Sleep timer mode - determines when playback should stop
    4/// TRACES: UR-026 | DR-029
    5#[derive(specta::Type, Debug, Clone, Serialize, Deserialize, PartialEq)]
    6#[serde(tag = "kind", rename_all = "camelCase")]
    7pub enum SleepTimerMode {
    8    /// Timer is off
    9    Off,
    10    /// Stop after a specific time duration
    11    Time {
    12        #[serde(rename = "endTime")]
    13        end_time: i64, // Unix timestamp in milliseconds
    14    },
    15    /// Stop at the end of current track
    16    EndOfTrack,
    17    /// Stop after N more episodes complete (TV episodes only, not audio tracks)
    18    Episodes { remaining: u32 },
    19}
    20
    21/// Sleep timer state
    22#[derive(specta::Type, Debug, Clone, Serialize, Deserialize)]
    23#[serde(rename_all = "camelCase")]
    24pub struct SleepTimerState {
    25    pub mode: SleepTimerMode,
    26    pub remaining_seconds: u32,
    27}
    28
    29impl Default for SleepTimerState {
    30    fn default() -> Self {
    31        Self {
    32            mode: SleepTimerMode::Off,
    33            remaining_seconds: 0,
    34        }
    35    }
    36}
    37
    38impl SleepTimerState {
    39    /// Check if timer is active
    40    pub fn is_active(&self) -> bool {
    41        !matches!(self.mode, SleepTimerMode::Off)
    42    }
    43
    44    /// Update remaining seconds for time-based timer
    45    pub fn update_remaining_seconds(&mut self) {
    46        if let SleepTimerMode::Time { end_time } = self.mode {
    47            let now = chrono::Utc::now().timestamp_millis();
    48            self.remaining_seconds = if now >= end_time {
    49                0
    50            } else {
    51                ((end_time - now) / 1000).max(0) as u32
    52            };
    53        }
    54    }
    55
    56    /// Decrement episode counter, returns true if should stop
    57    /// Only counts TV episodes, not audio tracks
    58    pub fn decrement_episode(&mut self) -> bool {
    59        match &mut self.mode {
    60            SleepTimerMode::Episodes { remaining } => {
    61                if *remaining <= 1 {
    62                    self.mode = SleepTimerMode::Off;
    63                    self.remaining_seconds = 0;
    64                    true
    65                } else {
    66                    *remaining -= 1;
    67                    false
    68                }
    69            }
    70            _ => false,
    71        }
    72    }
    73
    74    /// Cancel the timer
    75    pub fn cancel(&mut self) {
    76        self.mode = SleepTimerMode::Off;
    77        self.remaining_seconds = 0;
    78    }
    79}
    80
    81// TRACES: UR-026 | DR-029 | UT-012
    82#[cfg(test)]
    83mod tests {
    84    use super::*;
    85
    86    #[test]
    87    fn test_sleep_timer_episode_decrement() {
    88        let mut timer = SleepTimerState {
    89            mode: SleepTimerMode::Episodes { remaining: 2 },
    90            remaining_seconds: 0,
    91        };
    92
    93        assert!(!timer.decrement_episode());
    94        assert!(matches!(
    95            timer.mode,
    96            SleepTimerMode::Episodes { remaining: 1 }
    97        ));
    98
    99        assert!(timer.decrement_episode());
    100        assert!(matches!(timer.mode, SleepTimerMode::Off));
    101    }
    102
    103    #[test]
    104    fn test_sleep_timer_is_active() {
    105        let timer = SleepTimerState::default();
    106        assert!(!timer.is_active());
    107
    108        let timer = SleepTimerState {
    109            mode: SleepTimerMode::Time { end_time: 0 },
    110            remaining_seconds: 0,
    111        };
    112        assert!(timer.is_active());
    113
    114        let timer = SleepTimerState {
    115            mode: SleepTimerMode::EndOfTrack,
    116            remaining_seconds: 0,
    117        };
    118        assert!(timer.is_active());
    119
    120        let timer = SleepTimerState {
    121            mode: SleepTimerMode::Episodes { remaining: 3 },
    122            remaining_seconds: 0,
    123        };
    124        assert!(timer.is_active());
    125    }
    126
    127    #[test]
    128    fn test_sleep_timer_cancel() {
    129        let mut timer = SleepTimerState {
    130            mode: SleepTimerMode::Episodes { remaining: 5 },
    131            remaining_seconds: 300,
    132        };
    133
    134        timer.cancel();
    135        assert!(matches!(timer.mode, SleepTimerMode::Off));
    136        assert_eq!(timer.remaining_seconds, 0);
    137    }
    138
    139    #[test]
    140    fn test_update_remaining_seconds() {
    141        let end_time = chrono::Utc::now().timestamp_millis() + 30000; // 30 seconds from now
    142        let mut timer = SleepTimerState {
    143            mode: SleepTimerMode::Time { end_time },
    144            remaining_seconds: 0,
    145        };
    146
    147        timer.update_remaining_seconds();
    148        // Should be approximately 30 seconds (allow for small time difference)
    149        assert!(timer.remaining_seconds >= 29 && timer.remaining_seconds <= 31);
    150    }
    151}