snapcast_client/
time_provider.rs1use std::sync::atomic::{AtomicI64, Ordering};
8use std::time::{Duration, Instant};
9
10use snapcast_proto::Timeval;
11
12use crate::double_buffer::DoubleBuffer;
13
14pub struct TimeProvider {
16 diff_buffer: DoubleBuffer,
17 diff_to_server_usec: AtomicI64,
18 last_sync: Option<Instant>,
19}
20
21impl Default for TimeProvider {
22 fn default() -> Self {
23 Self::new()
24 }
25}
26
27impl TimeProvider {
28 pub fn new() -> Self {
30 Self {
31 diff_buffer: DoubleBuffer::new(200),
32 diff_to_server_usec: AtomicI64::new(0),
33 last_sync: None,
34 }
35 }
36
37 pub fn set_diff(&mut self, c2s: &Timeval, s2c: &Timeval) {
42 let diff_ms = (f64::from(c2s.sec) / 2.0 - f64::from(s2c.sec) / 2.0) * 1000.0
43 + (f64::from(c2s.usec) / 2.0 - f64::from(s2c.usec) / 2.0) / 1000.0;
44 tracing::trace!(diff_ms, "set_diff");
45 self.set_diff_ms(diff_ms);
46 }
47
48 pub fn set_diff_ms(&mut self, ms: f64) {
50 let now = Instant::now();
51
52 if let Some(last) = self.last_sync
54 && now.duration_since(last) > Duration::from_secs(60)
55 && !self.diff_buffer.is_empty()
56 {
57 self.diff_to_server_usec
58 .store((ms * 1000.0) as i64, Ordering::Relaxed);
59 self.diff_buffer.clear();
60 }
61 self.last_sync = Some(now);
62
63 self.diff_buffer.add((ms * 1000.0) as i64);
64 let median = self.diff_buffer.median_simple();
65 self.diff_to_server_usec.store(median, Ordering::Relaxed);
66 }
67
68 pub fn diff_to_server_usec(&self) -> i64 {
70 self.diff_to_server_usec.load(Ordering::Relaxed)
71 }
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77
78 #[test]
79 fn initial_diff_is_zero() {
80 let tp = TimeProvider::new();
81 assert_eq!(tp.diff_to_server_usec(), 0);
82 }
83
84 #[test]
85 fn set_diff_from_latency_pair() {
86 let mut tp = TimeProvider::new();
87
88 let c2s = Timeval {
90 sec: 0,
91 usec: 10_000,
92 };
93 let s2c = Timeval {
94 sec: 0,
95 usec: 8_000,
96 };
97 tp.set_diff(&c2s, &s2c);
98 assert_eq!(tp.diff_to_server_usec(), 1000);
99 }
100
101 #[test]
102 fn median_stabilizes() {
103 let mut tp = TimeProvider::new();
104
105 for _ in 0..10 {
107 tp.set_diff_ms(5.0); }
109 tp.set_diff_ms(100.0); assert_eq!(tp.diff_to_server_usec(), 5000);
113 }
114
115 #[test]
116 fn negative_diff() {
117 let mut tp = TimeProvider::new();
118 tp.set_diff_ms(-3.5);
119 assert_eq!(tp.diff_to_server_usec(), -3500);
120 }
121
122 #[test]
123 fn set_diff_symmetric_latency_cancels() {
124 let mut tp = TimeProvider::new();
125
126 let c2s = Timeval { sec: 0, usec: 5000 };
128 let s2c = Timeval { sec: 0, usec: 5000 };
129 tp.set_diff(&c2s, &s2c);
130 assert_eq!(tp.diff_to_server_usec(), 0);
131 }
132}