Skip to main content

snapcast_server/
time.rs

1//! Time utilities — server timestamp generation using monotonic clock.
2//!
3//! Must use the same clock as the client (`mach_continuous_time` on macOS,
4//! `CLOCK_MONOTONIC` on Linux) for time sync to work.
5
6/// Current monotonic time in microseconds.
7pub fn now_usec() -> i64 {
8    monotonic_usec()
9}
10
11/// Monotonic microsecond clock (same as client).
12#[allow(unsafe_code)]
13fn monotonic_usec() -> i64 {
14    #[cfg(target_os = "macos")]
15    {
16        unsafe extern "C" {
17            fn mach_continuous_time() -> u64;
18            fn mach_timebase_info(info: *mut MachTimebaseInfo) -> i32;
19        }
20        #[repr(C)]
21        struct MachTimebaseInfo {
22            numer: u32,
23            denom: u32,
24        }
25        static TIMEBASE: std::sync::OnceLock<(u32, u32)> = std::sync::OnceLock::new();
26        let (numer, denom) = *TIMEBASE.get_or_init(|| {
27            let mut info = MachTimebaseInfo { numer: 0, denom: 0 };
28            unsafe {
29                mach_timebase_info(&mut info);
30            }
31            (info.numer, info.denom)
32        });
33        let ticks = unsafe { mach_continuous_time() };
34        let nanos = ticks as i128 * numer as i128 / denom as i128;
35        (nanos / 1_000) as i64
36    }
37    #[cfg(all(unix, not(target_os = "macos")))]
38    {
39        let mut ts = libc::timespec {
40            tv_sec: 0,
41            tv_nsec: 0,
42        };
43        unsafe {
44            libc::clock_gettime(libc::CLOCK_MONOTONIC, &mut ts);
45        }
46        ts.tv_sec * 1_000_000 + ts.tv_nsec / 1_000
47    }
48    #[cfg(not(unix))]
49    {
50        use std::time::{SystemTime, UNIX_EPOCH};
51        SystemTime::now()
52            .duration_since(UNIX_EPOCH)
53            .unwrap_or_default()
54            .as_micros() as i64
55    }
56}
57
58/// Generates evenly-spaced timestamps based on sample count.
59/// This matches the C++ server behavior: timestamps reflect when audio
60/// *should* be played, not when it was read from the source.
61pub struct ChunkTimestamper {
62    start_usec: i64,
63    samples_written: u64,
64    rate: u32,
65}
66
67impl ChunkTimestamper {
68    /// Create a new timestamper anchored at the current time.
69    pub fn new(rate: u32) -> Self {
70        Self {
71            start_usec: now_usec(),
72            samples_written: 0,
73            rate,
74        }
75    }
76
77    /// Get the timestamp for the next chunk of `frames` frames.
78    pub fn next(&mut self, frames: u32) -> i64 {
79        let ts = self.start_usec + (self.samples_written as i64 * 1_000_000) / self.rate as i64;
80        self.samples_written += frames as u64;
81        ts
82    }
83
84    /// Reset the timestamper (e.g. on stream restart).
85    pub fn reset(&mut self) {
86        self.start_usec = now_usec();
87        self.samples_written = 0;
88    }
89}