spotify_cli/cli/commands/player/
state.rs1use crate::endpoints::player::{get_available_devices, transfer_playback};
4use crate::http::api::SpotifyApi;
5use crate::io::output::{ErrorKind, Response};
6use crate::types::{Device, DevicesResponse};
7
8use crate::cli::commands::{now_playing, with_client};
9
10fn find_device_by_name(devices: &[Device], name: &str) -> Option<String> {
12 let name_lower = name.to_lowercase();
13 devices
14 .iter()
15 .find(|d| d.name.to_lowercase().contains(&name_lower))
16 .and_then(|d| d.id.clone())
17}
18
19pub async fn player_status(id_only: Option<&str>) -> Response {
20 with_client(|client| async move {
21 match now_playing::get_state(&client).await {
22 Ok(state) => {
23 if let Some(id_type) = id_only {
24 let id = match id_type {
25 "track" => state.item.as_ref().map(|t| t.id.as_str()),
26 "album" => state
27 .item
28 .as_ref()
29 .and_then(|t| t.album.as_ref())
30 .map(|a| a.id.as_str()),
31 "artist" => state
32 .item
33 .as_ref()
34 .and_then(|t| t.artists.as_ref())
35 .and_then(|artists| artists.first())
36 .map(|a| a.id.as_str()),
37 _ => None,
38 };
39 return match id {
40 Some(id) => Response::success(200, id),
41 None => Response::err(404, "Nothing currently playing", ErrorKind::Player),
42 };
43 }
44
45 let message = if state.is_playing {
46 "Playing"
47 } else {
48 "Paused"
49 };
50 Response::success_with_payload(
51 200,
52 message,
53 serde_json::to_value(&state).expect("PlaybackState serializes to JSON"),
54 )
55 }
56 Err(e) => {
57 if id_only.is_some() {
58 return e;
59 }
60 Response::success_with_payload(
61 204,
62 "No active playback",
63 serde_json::json!({
64 "is_playing": false,
65 "active": false
66 }),
67 )
68 }
69 }
70 })
71 .await
72}
73
74pub async fn player_devices_list() -> Response {
75 with_client(|client| async move {
76 match get_available_devices::get_available_devices(&client).await {
77 Ok(Some(payload)) => Response::success_with_payload(200, "Available devices", payload),
78 Ok(None) => Response::success_with_payload(
79 200,
80 "No devices available",
81 serde_json::json!({ "devices": [] }),
82 ),
83 Err(e) => Response::from_http_error(&e, "Failed to get devices"),
84 }
85 })
86 .await
87}
88
89pub async fn player_devices_transfer(device: &str) -> Response {
90 let device = device.to_string();
91 with_client(|client| async move {
92 if transfer_playback::transfer_playback(&client, &device)
93 .await
94 .is_ok()
95 {
96 return Response::success(204, "Playback transferred");
97 }
98
99 match get_available_devices::get_available_devices(&client).await {
100 Ok(Some(payload)) => {
101 let devices: Result<DevicesResponse, _> = serde_json::from_value(payload);
102 match devices {
103 Ok(resp) => {
104 if let Some(id) = find_device_by_name(&resp.devices, &device) {
105 return do_transfer(&client, &id).await;
106 }
107 Response::err(404, "Device not found", ErrorKind::NotFound)
108 }
109 Err(_) => Response::err(500, "Failed to parse devices", ErrorKind::Api),
110 }
111 }
112 Ok(None) => Response::err(404, "No devices available", ErrorKind::Player),
113 Err(e) => Response::from_http_error(&e, "Failed to get devices"),
114 }
115 })
116 .await
117}
118
119async fn do_transfer(client: &SpotifyApi, device_id: &str) -> Response {
120 match transfer_playback::transfer_playback(client, device_id).await {
121 Ok(_) => Response::success(204, "Playback transferred"),
122 Err(e) => Response::from_http_error(&e, "Failed to transfer playback"),
123 }
124}