spotify_cli/rpc/
events.rs1use std::time::Duration;
6
7use serde_json::Value;
8use tokio::sync::broadcast;
9use tokio::time::interval;
10use tracing::{debug, trace, warn};
11
12use crate::cli::commands::get_authenticated_client;
13use crate::endpoints::player::get_playback_state;
14
15use super::protocol::RpcNotification;
16
17#[derive(Debug, Clone, PartialEq, Eq)]
19pub enum EventType {
20 TrackChanged,
21 PlaybackStateChanged,
22 VolumeChanged,
23 ShuffleChanged,
24 RepeatChanged,
25 DeviceChanged,
26}
27
28#[derive(Debug, Clone, Default)]
30struct PlaybackSnapshot {
31 track_id: Option<String>,
32 is_playing: bool,
33 volume: u8,
34 shuffle: bool,
35 repeat: String,
36 device_id: Option<String>,
37}
38
39impl PlaybackSnapshot {
40 fn from_json(state: &Value) -> Self {
41 Self {
42 track_id: state
43 .get("item")
44 .and_then(|i| i.get("id"))
45 .and_then(|v| v.as_str())
46 .map(String::from),
47 is_playing: state
48 .get("is_playing")
49 .and_then(|v| v.as_bool())
50 .unwrap_or(false),
51 volume: state
52 .get("device")
53 .and_then(|d| d.get("volume_percent"))
54 .and_then(|v| v.as_u64())
55 .unwrap_or(0) as u8,
56 shuffle: state
57 .get("shuffle_state")
58 .and_then(|v| v.as_bool())
59 .unwrap_or(false),
60 repeat: state
61 .get("repeat_state")
62 .and_then(|v| v.as_str())
63 .unwrap_or("off")
64 .to_string(),
65 device_id: state
66 .get("device")
67 .and_then(|d| d.get("id"))
68 .and_then(|v| v.as_str())
69 .map(String::from),
70 }
71 }
72}
73
74pub struct EventPoller {
76 event_tx: broadcast::Sender<RpcNotification>,
77 poll_interval: Duration,
78}
79
80impl EventPoller {
81 pub fn new(event_tx: broadcast::Sender<RpcNotification>) -> Self {
82 Self {
83 event_tx,
84 poll_interval: Duration::from_secs(2),
85 }
86 }
87
88 pub fn with_interval(mut self, interval: Duration) -> Self {
90 self.poll_interval = interval;
91 self
92 }
93
94 pub async fn run(&self) {
96 let mut interval = interval(self.poll_interval);
97 let mut last_state = PlaybackSnapshot::default();
98
99 loop {
100 interval.tick().await;
101
102 match self.poll_playback_state().await {
103 Some(current) => {
104 self.detect_and_broadcast_changes(&last_state, ¤t)
105 .await;
106 last_state = current;
107 }
108 None => {
109 trace!("No playback state available");
110 }
111 }
112 }
113 }
114
115 async fn poll_playback_state(&self) -> Option<PlaybackSnapshot> {
116 let client = match get_authenticated_client().await {
117 Ok(c) => c,
118 Err(_) => {
119 trace!("Not authenticated, skipping poll");
120 return None;
121 }
122 };
123
124 match get_playback_state::get_playback_state(&client).await {
125 Ok(Some(state)) => Some(PlaybackSnapshot::from_json(&state)),
126 Ok(None) => None,
127 Err(e) => {
128 warn!(error = %e, "Failed to poll playback state");
129 None
130 }
131 }
132 }
133
134 async fn detect_and_broadcast_changes(&self, old: &PlaybackSnapshot, new: &PlaybackSnapshot) {
135 if old.track_id != new.track_id {
137 debug!(old = ?old.track_id, new = ?new.track_id, "Track changed");
138 self.broadcast(
139 "event.trackChanged",
140 serde_json::json!({
141 "track_id": new.track_id,
142 }),
143 );
144 }
145
146 if old.is_playing != new.is_playing {
148 debug!(is_playing = new.is_playing, "Playback state changed");
149 self.broadcast(
150 "event.playbackStateChanged",
151 serde_json::json!({
152 "is_playing": new.is_playing,
153 }),
154 );
155 }
156
157 if old.volume != new.volume {
159 debug!(volume = new.volume, "Volume changed");
160 self.broadcast(
161 "event.volumeChanged",
162 serde_json::json!({
163 "volume": new.volume,
164 }),
165 );
166 }
167
168 if old.shuffle != new.shuffle {
170 debug!(shuffle = new.shuffle, "Shuffle changed");
171 self.broadcast(
172 "event.shuffleChanged",
173 serde_json::json!({
174 "shuffle": new.shuffle,
175 }),
176 );
177 }
178
179 if old.repeat != new.repeat {
181 debug!(repeat = %new.repeat, "Repeat changed");
182 self.broadcast(
183 "event.repeatChanged",
184 serde_json::json!({
185 "repeat": new.repeat,
186 }),
187 );
188 }
189
190 if old.device_id != new.device_id {
192 debug!(device = ?new.device_id, "Device changed");
193 self.broadcast(
194 "event.deviceChanged",
195 serde_json::json!({
196 "device_id": new.device_id,
197 }),
198 );
199 }
200 }
201
202 fn broadcast(&self, method: &str, params: Value) {
203 let notification = RpcNotification::new(method, Some(params));
204 let _ = self.event_tx.send(notification);
206 }
207}