spotify_cli/cli/commands/player/
state.rs

1//! Player state commands: status, devices
2
3use 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
10/// Find a device ID by name (case-insensitive partial match)
11fn 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}