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}