1#![allow(clippy::module_name_repetitions)]
2use anyhow::anyhow;
3
4#[allow(clippy::pedantic)]
6mod protobuf {
7 tonic::include_proto!("player");
8}
9
10pub use protobuf::*;
11
12impl From<protobuf::Duration> for std::time::Duration {
14 fn from(value: protobuf::Duration) -> Self {
15 std::time::Duration::new(value.secs, value.nanos)
16 }
17}
18
19impl From<std::time::Duration> for protobuf::Duration {
20 fn from(value: std::time::Duration) -> Self {
21 Self {
22 secs: value.as_secs(),
23 nanos: value.subsec_nanos(),
24 }
25 }
26}
27
28pub type PlayerTimeUnit = std::time::Duration;
30
31#[derive(Debug, Clone, Copy, PartialEq)]
33pub struct PlayerProgress {
34 pub position: Option<PlayerTimeUnit>,
35 pub total_duration: Option<PlayerTimeUnit>,
37}
38
39impl From<protobuf::PlayerTime> for PlayerProgress {
40 fn from(value: protobuf::PlayerTime) -> Self {
41 Self {
42 position: value.position.map(Into::into),
43 total_duration: value.total_duration.map(Into::into),
44 }
45 }
46}
47
48impl From<PlayerProgress> for protobuf::PlayerTime {
49 fn from(value: PlayerProgress) -> Self {
50 Self {
51 position: value.position.map(Into::into),
52 total_duration: value.total_duration.map(Into::into),
53 }
54 }
55}
56
57#[derive(Debug, Clone, PartialEq)]
58pub struct TrackChangedInfo {
59 pub current_track_index: u64,
61 pub current_track_updated: bool,
63 pub title: Option<String>,
65 pub progress: Option<PlayerProgress>,
67}
68
69#[derive(Debug, Clone, PartialEq)]
70pub enum UpdateEvents {
71 MissedEvents { amount: u64 },
72 VolumeChanged { volume: u16 },
73 SpeedChanged { speed: i32 },
74 PlayStateChanged { playing: u32 },
75 TrackChanged(TrackChangedInfo),
76 GaplessChanged { gapless: bool },
77}
78
79type StreamTypes = protobuf::stream_updates::Type;
80
81impl From<UpdateEvents> for protobuf::StreamUpdates {
83 fn from(value: UpdateEvents) -> Self {
84 let val = match value {
85 UpdateEvents::MissedEvents { amount } => {
86 StreamTypes::MissedEvents(UpdateMissedEvents { amount })
87 }
88 UpdateEvents::VolumeChanged { volume } => {
89 StreamTypes::VolumeChanged(UpdateVolumeChanged {
90 msg: Some(VolumeReply {
91 volume: u32::from(volume),
92 }),
93 })
94 }
95 UpdateEvents::SpeedChanged { speed } => StreamTypes::SpeedChanged(UpdateSpeedChanged {
96 msg: Some(SpeedReply { speed }),
97 }),
98 UpdateEvents::PlayStateChanged { playing } => {
99 StreamTypes::PlayStateChanged(UpdatePlayStateChanged {
100 msg: Some(PlayState { status: playing }),
101 })
102 }
103 UpdateEvents::TrackChanged(info) => StreamTypes::TrackChanged(UpdateTrackChanged {
104 current_track_index: info.current_track_index,
105 current_track_updated: info.current_track_updated,
106 optional_title: info
107 .title
108 .map(protobuf::update_track_changed::OptionalTitle::Title),
109 progress: info.progress.map(Into::into),
110 }),
111 UpdateEvents::GaplessChanged { gapless } => {
112 StreamTypes::GaplessChanged(UpdateGaplessChanged {
113 msg: Some(GaplessState { gapless }),
114 })
115 }
116 };
117
118 Self { r#type: Some(val) }
119 }
120}
121
122impl TryFrom<protobuf::StreamUpdates> for UpdateEvents {
124 type Error = anyhow::Error;
125
126 fn try_from(value: protobuf::StreamUpdates) -> Result<Self, Self::Error> {
127 let value = unwrap_msg(value.r#type, "StreamUpdates.type")?;
128
129 let res = match value {
130 StreamTypes::VolumeChanged(ev) => Self::VolumeChanged {
131 volume: clamp_u16(
132 unwrap_msg(ev.msg, "StreamUpdates.types.volume_changed.msg")?.volume,
133 ),
134 },
135 StreamTypes::SpeedChanged(ev) => Self::SpeedChanged {
136 speed: unwrap_msg(ev.msg, "StreamUpdates.types.speed_changed.msg")?.speed,
137 },
138 StreamTypes::PlayStateChanged(ev) => Self::PlayStateChanged {
139 playing: unwrap_msg(ev.msg, "StreamUpdates.types.play_state_changed.msg")?.status,
140 },
141 StreamTypes::MissedEvents(ev) => Self::MissedEvents { amount: ev.amount },
142 StreamTypes::TrackChanged(ev) => Self::TrackChanged(TrackChangedInfo {
143 current_track_index: ev.current_track_index,
144 current_track_updated: ev.current_track_updated,
145 title: ev.optional_title.map(|v| {
146 let protobuf::update_track_changed::OptionalTitle::Title(v) = v;
147 v
148 }),
149 progress: ev.progress.map(Into::into),
150 }),
151 StreamTypes::GaplessChanged(ev) => Self::GaplessChanged {
152 gapless: unwrap_msg(ev.msg, "StreamUpdates.types.gapless_changed.msg")?.gapless,
153 },
154 };
155
156 Ok(res)
157 }
158}
159
160fn unwrap_msg<T>(opt: Option<T>, place: &str) -> Result<T, anyhow::Error> {
162 match opt {
163 Some(val) => Ok(val),
164 None => Err(anyhow!("Got \"None\" in grpc \"{place}\"!")),
165 }
166}
167
168#[allow(clippy::cast_possible_truncation)]
169fn clamp_u16(val: u32) -> u16 {
170 val.min(u32::from(u16::MAX)) as u16
171}