1#![allow(clippy::module_name_repetitions)]
2use anyhow::{anyhow, Context};
3
4#[allow(clippy::pedantic)]
6mod protobuf {
7 tonic::include_proto!("player");
8}
9
10pub use protobuf::*;
11
12use crate::config::v2::server::LoopMode;
13
14impl From<protobuf::Duration> for std::time::Duration {
16 fn from(value: protobuf::Duration) -> Self {
17 std::time::Duration::new(value.secs, value.nanos)
18 }
19}
20
21impl From<std::time::Duration> for protobuf::Duration {
22 fn from(value: std::time::Duration) -> Self {
23 Self {
24 secs: value.as_secs(),
25 nanos: value.subsec_nanos(),
26 }
27 }
28}
29
30pub type PlayerTimeUnit = std::time::Duration;
32
33#[derive(Clone, Copy, Default, PartialEq, Eq, Debug)]
34pub enum RunningStatus {
35 #[default]
36 Stopped,
37 Running,
38 Paused,
39}
40
41impl RunningStatus {
42 #[must_use]
43 pub fn as_u32(&self) -> u32 {
44 match self {
45 RunningStatus::Stopped => 0,
46 RunningStatus::Running => 1,
47 RunningStatus::Paused => 2,
48 }
49 }
50
51 #[must_use]
52 pub fn from_u32(status: u32) -> Self {
53 match status {
54 1 => RunningStatus::Running,
55 2 => RunningStatus::Paused,
56 _ => RunningStatus::Stopped,
57 }
58 }
59}
60
61impl std::fmt::Display for RunningStatus {
62 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63 match self {
64 Self::Running => write!(f, "Running"),
65 Self::Stopped => write!(f, "Stopped"),
66 Self::Paused => write!(f, "Paused"),
67 }
68 }
69}
70
71#[derive(Debug, Clone, Copy, PartialEq)]
73pub struct PlayerProgress {
74 pub position: Option<PlayerTimeUnit>,
75 pub total_duration: Option<PlayerTimeUnit>,
77}
78
79impl From<protobuf::PlayerTime> for PlayerProgress {
80 fn from(value: protobuf::PlayerTime) -> Self {
81 Self {
82 position: value.position.map(Into::into),
83 total_duration: value.total_duration.map(Into::into),
84 }
85 }
86}
87
88impl From<PlayerProgress> for protobuf::PlayerTime {
89 fn from(value: PlayerProgress) -> Self {
90 Self {
91 position: value.position.map(Into::into),
92 total_duration: value.total_duration.map(Into::into),
93 }
94 }
95}
96
97#[derive(Debug, Clone, PartialEq)]
98pub struct TrackChangedInfo {
99 pub current_track_index: u64,
101 pub current_track_updated: bool,
103 pub title: Option<String>,
105 pub progress: Option<PlayerProgress>,
107}
108
109#[derive(Debug, Clone, PartialEq)]
110pub enum UpdateEvents {
111 MissedEvents { amount: u64 },
112 VolumeChanged { volume: u16 },
113 SpeedChanged { speed: i32 },
114 PlayStateChanged { playing: u32 },
115 TrackChanged(TrackChangedInfo),
116 GaplessChanged { gapless: bool },
117 PlaylistChanged(UpdatePlaylistEvents),
118}
119
120type StreamTypes = protobuf::stream_updates::Type;
121
122impl From<UpdateEvents> for protobuf::StreamUpdates {
124 fn from(value: UpdateEvents) -> Self {
125 let val = match value {
126 UpdateEvents::MissedEvents { amount } => {
127 StreamTypes::MissedEvents(UpdateMissedEvents { amount })
128 }
129 UpdateEvents::VolumeChanged { volume } => {
130 StreamTypes::VolumeChanged(UpdateVolumeChanged {
131 msg: Some(VolumeReply {
132 volume: u32::from(volume),
133 }),
134 })
135 }
136 UpdateEvents::SpeedChanged { speed } => StreamTypes::SpeedChanged(UpdateSpeedChanged {
137 msg: Some(SpeedReply { speed }),
138 }),
139 UpdateEvents::PlayStateChanged { playing } => {
140 StreamTypes::PlayStateChanged(UpdatePlayStateChanged {
141 msg: Some(PlayState { status: playing }),
142 })
143 }
144 UpdateEvents::TrackChanged(info) => StreamTypes::TrackChanged(UpdateTrackChanged {
145 current_track_index: info.current_track_index,
146 current_track_updated: info.current_track_updated,
147 optional_title: info
148 .title
149 .map(protobuf::update_track_changed::OptionalTitle::Title),
150 progress: info.progress.map(Into::into),
151 }),
152 UpdateEvents::GaplessChanged { gapless } => {
153 StreamTypes::GaplessChanged(UpdateGaplessChanged {
154 msg: Some(GaplessState { gapless }),
155 })
156 }
157 UpdateEvents::PlaylistChanged(ev) => StreamTypes::PlaylistChanged(ev.into()),
158 };
159
160 Self { r#type: Some(val) }
161 }
162}
163
164impl TryFrom<protobuf::StreamUpdates> for UpdateEvents {
166 type Error = anyhow::Error;
167
168 fn try_from(value: protobuf::StreamUpdates) -> Result<Self, Self::Error> {
169 let value = unwrap_msg(value.r#type, "StreamUpdates.type")?;
170
171 let res = match value {
172 StreamTypes::VolumeChanged(ev) => Self::VolumeChanged {
173 volume: clamp_u16(
174 unwrap_msg(ev.msg, "StreamUpdates.types.volume_changed.msg")?.volume,
175 ),
176 },
177 StreamTypes::SpeedChanged(ev) => Self::SpeedChanged {
178 speed: unwrap_msg(ev.msg, "StreamUpdates.types.speed_changed.msg")?.speed,
179 },
180 StreamTypes::PlayStateChanged(ev) => Self::PlayStateChanged {
181 playing: unwrap_msg(ev.msg, "StreamUpdates.types.play_state_changed.msg")?.status,
182 },
183 StreamTypes::MissedEvents(ev) => Self::MissedEvents { amount: ev.amount },
184 StreamTypes::TrackChanged(ev) => Self::TrackChanged(TrackChangedInfo {
185 current_track_index: ev.current_track_index,
186 current_track_updated: ev.current_track_updated,
187 title: ev.optional_title.map(|v| {
188 let protobuf::update_track_changed::OptionalTitle::Title(v) = v;
189 v
190 }),
191 progress: ev.progress.map(Into::into),
192 }),
193 StreamTypes::GaplessChanged(ev) => Self::GaplessChanged {
194 gapless: unwrap_msg(ev.msg, "StreamUpdates.types.gapless_changed.msg")?.gapless,
195 },
196 StreamTypes::PlaylistChanged(ev) => Self::PlaylistChanged(
197 ev.try_into()
198 .context("In \"StreamUpdates.types.playlist_changed\"")?,
199 ),
200 };
201
202 Ok(res)
203 }
204}
205
206#[derive(Debug, Clone, PartialEq)]
207pub struct PlaylistAddTrackInfo {
208 pub at_index: u64,
211 pub title: Option<String>,
212 pub duration: PlayerTimeUnit,
214 pub trackid: playlist_helpers::PlaylistTrackSource,
215}
216
217#[derive(Debug, Clone, PartialEq)]
218pub struct PlaylistRemoveTrackInfo {
219 pub at_index: u64,
221 pub trackid: playlist_helpers::PlaylistTrackSource,
223}
224
225#[derive(Debug, Clone, PartialEq)]
226pub struct PlaylistLoopModeInfo {
227 pub mode: u32,
229}
230
231impl From<LoopMode> for PlaylistLoopModeInfo {
232 fn from(value: LoopMode) -> Self {
233 Self {
234 mode: u32::from(value.discriminant()),
235 }
236 }
237}
238
239#[derive(Debug, Clone, PartialEq)]
240pub struct PlaylistSwapInfo {
241 pub index_a: u64,
242 pub index_b: u64,
243}
244
245#[derive(Debug, Clone, PartialEq)]
246pub struct PlaylistShuffledInfo {
247 pub tracks: PlaylistTracks,
248}
249
250#[derive(Debug, Clone, PartialEq)]
252pub enum UpdatePlaylistEvents {
253 PlaylistAddTrack(PlaylistAddTrackInfo),
254 PlaylistRemoveTrack(PlaylistRemoveTrackInfo),
255 PlaylistCleared,
256 PlaylistLoopMode(PlaylistLoopModeInfo),
257 PlaylistSwapTracks(PlaylistSwapInfo),
258 PlaylistShuffled(PlaylistShuffledInfo),
259}
260
261type PPlaylistTypes = protobuf::update_playlist::Type;
262
263impl From<UpdatePlaylistEvents> for protobuf::UpdatePlaylist {
265 fn from(value: UpdatePlaylistEvents) -> Self {
266 let val = match value {
267 UpdatePlaylistEvents::PlaylistAddTrack(vals) => {
268 PPlaylistTypes::AddTrack(protobuf::PlaylistAddTrack {
269 at_index: vals.at_index,
270 optional_title: vals
271 .title
272 .map(protobuf::playlist_add_track::OptionalTitle::Title),
273 duration: Some(vals.duration.into()),
274 id: Some(vals.trackid.into()),
275 })
276 }
277 UpdatePlaylistEvents::PlaylistRemoveTrack(vals) => {
278 PPlaylistTypes::RemoveTrack(protobuf::PlaylistRemoveTrack {
279 at_index: vals.at_index,
280 id: Some(vals.trackid.into()),
281 })
282 }
283 UpdatePlaylistEvents::PlaylistCleared => PPlaylistTypes::Cleared(PlaylistCleared {}),
284 UpdatePlaylistEvents::PlaylistLoopMode(vals) => {
285 PPlaylistTypes::LoopMode(PlaylistLoopMode { mode: vals.mode })
286 }
287 UpdatePlaylistEvents::PlaylistSwapTracks(vals) => {
288 PPlaylistTypes::SwapTracks(protobuf::PlaylistSwapTracks {
289 index_a: vals.index_a,
290 index_b: vals.index_b,
291 })
292 }
293 UpdatePlaylistEvents::PlaylistShuffled(vals) => {
294 PPlaylistTypes::Shuffled(protobuf::PlaylistShuffled {
295 shuffled: Some(vals.tracks),
296 })
297 }
298 };
299
300 Self { r#type: Some(val) }
301 }
302}
303
304impl TryFrom<protobuf::UpdatePlaylist> for UpdatePlaylistEvents {
306 type Error = anyhow::Error;
307
308 fn try_from(value: protobuf::UpdatePlaylist) -> Result<Self, Self::Error> {
309 let value = unwrap_msg(value.r#type, "UpdatePlaylist.type")?;
310
311 let res = match value {
312 PPlaylistTypes::AddTrack(ev) => Self::PlaylistAddTrack(PlaylistAddTrackInfo {
313 at_index: ev.at_index,
314 title: ev.optional_title.map(|v| {
315 let protobuf::playlist_add_track::OptionalTitle::Title(v) = v;
316 v
317 }),
318 duration: unwrap_msg(ev.duration, "UpdatePlaylist.type.add_track.duration")?.into(),
319 trackid: unwrap_msg(
320 unwrap_msg(ev.id, "UpdatePlaylist.type.add_track.id")?.source,
321 "UpdatePlaylist.type.add_track.id.source",
322 )?
323 .try_into()?,
324 }),
325 PPlaylistTypes::RemoveTrack(ev) => Self::PlaylistRemoveTrack(PlaylistRemoveTrackInfo {
326 at_index: ev.at_index,
327 trackid: unwrap_msg(
328 unwrap_msg(ev.id, "UpdatePlaylist.type.remove_track.id")?.source,
329 "UpdatePlaylist.type.remove_track.id.source",
330 )?
331 .try_into()?,
332 }),
333 PPlaylistTypes::Cleared(_) => Self::PlaylistCleared,
334 PPlaylistTypes::LoopMode(ev) => {
335 Self::PlaylistLoopMode(PlaylistLoopModeInfo { mode: ev.mode })
336 }
337 PPlaylistTypes::SwapTracks(ev) => Self::PlaylistSwapTracks(PlaylistSwapInfo {
338 index_a: ev.index_a,
339 index_b: ev.index_b,
340 }),
341 PPlaylistTypes::Shuffled(ev) => {
342 let shuffled = unwrap_msg(ev.shuffled, "UpdatePlaylist.type.shuffled.shuffled")?;
343 Self::PlaylistShuffled(PlaylistShuffledInfo { tracks: shuffled })
344 }
345 };
346
347 Ok(res)
348 }
349}
350
351fn unwrap_msg<T>(opt: Option<T>, place: &str) -> Result<T, anyhow::Error> {
353 match opt {
354 Some(val) => Ok(val),
355 None => Err(anyhow!("Got \"None\" in grpc \"{place}\"!")),
356 }
357}
358
359#[allow(clippy::cast_possible_truncation)]
360fn clamp_u16(val: u32) -> u16 {
361 val.min(u32::from(u16::MAX)) as u16
362}
363
364pub mod playlist_helpers {
365 use anyhow::Context;
366
367 use super::{protobuf, unwrap_msg, PlaylistTracksToRemoveClear};
368
369 #[derive(Debug, Clone, PartialEq)]
371 pub enum PlaylistTrackSource {
372 Path(String),
373 Url(String),
374 PodcastUrl(String),
375 }
376
377 impl From<PlaylistTrackSource> for protobuf::track_id::Source {
378 fn from(value: PlaylistTrackSource) -> Self {
379 match value {
380 PlaylistTrackSource::Path(v) => Self::Path(v),
381 PlaylistTrackSource::Url(v) => Self::Url(v),
382 PlaylistTrackSource::PodcastUrl(v) => Self::PodcastUrl(v),
383 }
384 }
385 }
386
387 impl From<PlaylistTrackSource> for protobuf::TrackId {
388 fn from(value: PlaylistTrackSource) -> Self {
389 Self {
390 source: Some(value.into()),
391 }
392 }
393 }
394
395 impl TryFrom<protobuf::track_id::Source> for PlaylistTrackSource {
396 type Error = anyhow::Error;
397
398 fn try_from(value: protobuf::track_id::Source) -> Result<Self, Self::Error> {
399 Ok(match value {
400 protobuf::track_id::Source::Path(v) => Self::Path(v),
401 protobuf::track_id::Source::Url(v) => Self::Url(v),
402 protobuf::track_id::Source::PodcastUrl(v) => Self::PodcastUrl(v),
403 })
404 }
405 }
406
407 impl TryFrom<protobuf::TrackId> for PlaylistTrackSource {
408 type Error = anyhow::Error;
409
410 fn try_from(value: protobuf::TrackId) -> Result<Self, Self::Error> {
411 unwrap_msg(value.source, "TrackId.source").and_then(Self::try_from)
412 }
413 }
414
415 #[derive(Debug, Clone, PartialEq)]
417 pub struct PlaylistAddTrack {
418 pub at_index: u64,
419 pub tracks: Vec<PlaylistTrackSource>,
420 }
421
422 impl PlaylistAddTrack {
423 #[must_use]
424 pub fn new_single(at_index: u64, track: PlaylistTrackSource) -> Self {
425 Self {
426 at_index,
427 tracks: vec![track],
428 }
429 }
430
431 #[must_use]
432 pub fn new_vec(at_index: u64, tracks: Vec<PlaylistTrackSource>) -> Self {
433 Self { at_index, tracks }
434 }
435 }
436
437 impl From<PlaylistAddTrack> for protobuf::PlaylistTracksToAdd {
438 fn from(value: PlaylistAddTrack) -> Self {
439 Self {
440 at_index: value.at_index,
441 tracks: value.tracks.into_iter().map(Into::into).collect(),
442 }
443 }
444 }
445
446 impl TryFrom<protobuf::PlaylistTracksToAdd> for PlaylistAddTrack {
447 type Error = anyhow::Error;
448
449 fn try_from(value: protobuf::PlaylistTracksToAdd) -> Result<Self, Self::Error> {
450 let tracks = value
451 .tracks
452 .into_iter()
453 .map(|v| PlaylistTrackSource::try_from(v).context("PlaylistTracksToAdd.tracks"))
454 .collect::<Result<Vec<_>, anyhow::Error>>()?;
455
456 Ok(Self {
457 at_index: value.at_index,
458 tracks,
459 })
460 }
461 }
462
463 #[derive(Debug, Clone, PartialEq)]
465 pub struct PlaylistRemoveTrackIndexed {
466 pub at_index: u64,
467 pub tracks: Vec<PlaylistTrackSource>,
468 }
469
470 impl PlaylistRemoveTrackIndexed {
471 #[must_use]
472 pub fn new_single(at_index: u64, track: PlaylistTrackSource) -> Self {
473 Self {
474 at_index,
475 tracks: vec![track],
476 }
477 }
478
479 #[must_use]
480 pub fn new_vec(at_index: u64, tracks: Vec<PlaylistTrackSource>) -> Self {
481 Self { at_index, tracks }
482 }
483 }
484
485 impl From<PlaylistRemoveTrackIndexed> for protobuf::PlaylistTracksToRemoveIndexed {
486 fn from(value: PlaylistRemoveTrackIndexed) -> Self {
487 Self {
488 at_index: value.at_index,
489 tracks: value.tracks.into_iter().map(Into::into).collect(),
490 }
491 }
492 }
493
494 impl TryFrom<protobuf::PlaylistTracksToRemoveIndexed> for PlaylistRemoveTrackIndexed {
495 type Error = anyhow::Error;
496
497 fn try_from(value: protobuf::PlaylistTracksToRemoveIndexed) -> Result<Self, Self::Error> {
498 let tracks = value
499 .tracks
500 .into_iter()
501 .map(|v| {
502 PlaylistTrackSource::try_from(v).context("PlaylistTracksToRemoveIndexed.tracks")
503 })
504 .collect::<Result<Vec<_>, anyhow::Error>>()?;
505
506 Ok(Self {
507 at_index: value.at_index,
508 tracks,
509 })
510 }
511 }
512
513 #[derive(Debug, Clone, PartialEq)]
515 pub enum PlaylistRemoveTrackType {
516 Indexed(PlaylistRemoveTrackIndexed),
517 Clear,
518 }
519
520 type PToRemoveTypes = protobuf::playlist_tracks_to_remove::Type;
521
522 impl From<PlaylistRemoveTrackType> for protobuf::PlaylistTracksToRemove {
523 fn from(value: PlaylistRemoveTrackType) -> Self {
524 Self {
525 r#type: Some(match value {
526 PlaylistRemoveTrackType::Indexed(v) => PToRemoveTypes::Indexed(v.into()),
527 PlaylistRemoveTrackType::Clear => {
528 PToRemoveTypes::Clear(PlaylistTracksToRemoveClear {})
529 }
530 }),
531 }
532 }
533 }
534
535 impl TryFrom<protobuf::PlaylistTracksToRemove> for PlaylistRemoveTrackType {
536 type Error = anyhow::Error;
537
538 fn try_from(value: protobuf::PlaylistTracksToRemove) -> Result<Self, Self::Error> {
539 let value = unwrap_msg(value.r#type, "PlaylistTracksToRemove.type")?;
540
541 Ok(match value {
542 PToRemoveTypes::Indexed(v) => Self::Indexed(v.try_into()?),
543 PToRemoveTypes::Clear(_) => Self::Clear,
544 })
545 }
546 }
547
548 #[derive(Debug, Clone, PartialEq)]
550 pub struct PlaylistSwapTrack {
551 pub index_a: u64,
552 pub index_b: u64,
553 }
554
555 impl From<PlaylistSwapTrack> for protobuf::PlaylistSwapTracks {
556 fn from(value: PlaylistSwapTrack) -> Self {
557 Self {
558 index_a: value.index_a,
559 index_b: value.index_b,
560 }
561 }
562 }
563
564 impl TryFrom<protobuf::PlaylistSwapTracks> for PlaylistSwapTrack {
565 type Error = anyhow::Error;
566
567 fn try_from(value: protobuf::PlaylistSwapTracks) -> Result<Self, Self::Error> {
568 Ok(Self {
569 index_a: value.index_a,
570 index_b: value.index_b,
571 })
572 }
573 }
574
575 #[derive(Debug, Clone, PartialEq)]
577 pub struct PlaylistPlaySpecific {
578 pub track_index: u64,
579 pub id: PlaylistTrackSource,
580 }
581
582 impl From<PlaylistPlaySpecific> for protobuf::PlaylistPlaySpecific {
583 fn from(value: PlaylistPlaySpecific) -> Self {
584 Self {
585 track_index: value.track_index,
586 id: Some(value.id.into()),
587 }
588 }
589 }
590
591 impl TryFrom<protobuf::PlaylistPlaySpecific> for PlaylistPlaySpecific {
592 type Error = anyhow::Error;
593
594 fn try_from(value: protobuf::PlaylistPlaySpecific) -> Result<Self, Self::Error> {
595 Ok(Self {
596 track_index: value.track_index,
597 id: unwrap_msg(value.id, "PlaylistPlaySpecific.id").and_then(|v| {
598 PlaylistTrackSource::try_from(v).context("PlaylistPlaySpecific.id")
599 })?,
600 })
601 }
602 }
603}