1#![allow(clippy::module_name_repetitions)]
2use anyhow::{Context, anyhow, bail};
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
97impl TryFrom<protobuf::UpdateProgress> for PlayerProgress {
98 type Error = anyhow::Error;
99
100 fn try_from(value: protobuf::UpdateProgress) -> Result<Self, Self::Error> {
101 let Some(val) = value.progress else {
102 bail!("Expected \"UpdateProgress\" to contain \"Some(progress)\"");
103 };
104
105 Ok(Self::from(val))
106 }
107}
108
109impl From<PlayerProgress> for protobuf::UpdateProgress {
110 fn from(value: PlayerProgress) -> Self {
111 Self {
112 progress: Some(value.into()),
113 }
114 }
115}
116
117#[derive(Debug, Clone, PartialEq)]
118pub struct TrackChangedInfo {
119 pub current_track_index: u64,
121 pub current_track_updated: bool,
123 pub title: Option<String>,
125 pub progress: Option<PlayerProgress>,
127}
128
129#[derive(Debug, Clone, PartialEq)]
130pub enum UpdateEvents {
131 MissedEvents { amount: u64 },
132 VolumeChanged { volume: u16 },
133 SpeedChanged { speed: i32 },
134 PlayStateChanged { playing: u32 },
135 TrackChanged(TrackChangedInfo),
136 GaplessChanged { gapless: bool },
137 PlaylistChanged(UpdatePlaylistEvents),
138 Progress(PlayerProgress),
139}
140
141impl Eq for UpdateEvents {}
143
144type StreamTypes = protobuf::stream_updates::Type;
145
146impl From<UpdateEvents> for protobuf::StreamUpdates {
148 fn from(value: UpdateEvents) -> Self {
149 let val = match value {
150 UpdateEvents::MissedEvents { amount } => {
151 StreamTypes::MissedEvents(UpdateMissedEvents { amount })
152 }
153 UpdateEvents::VolumeChanged { volume } => {
154 StreamTypes::VolumeChanged(UpdateVolumeChanged {
155 msg: Some(VolumeReply {
156 volume: u32::from(volume),
157 }),
158 })
159 }
160 UpdateEvents::SpeedChanged { speed } => StreamTypes::SpeedChanged(UpdateSpeedChanged {
161 msg: Some(SpeedReply { speed }),
162 }),
163 UpdateEvents::PlayStateChanged { playing } => {
164 StreamTypes::PlayStateChanged(UpdatePlayStateChanged {
165 msg: Some(PlayState { status: playing }),
166 })
167 }
168 UpdateEvents::TrackChanged(info) => StreamTypes::TrackChanged(UpdateTrackChanged {
169 current_track_index: info.current_track_index,
170 current_track_updated: info.current_track_updated,
171 optional_title: info
172 .title
173 .map(protobuf::update_track_changed::OptionalTitle::Title),
174 progress: info.progress.map(Into::into),
175 }),
176 UpdateEvents::GaplessChanged { gapless } => {
177 StreamTypes::GaplessChanged(UpdateGaplessChanged {
178 msg: Some(GaplessState { gapless }),
179 })
180 }
181 UpdateEvents::PlaylistChanged(ev) => StreamTypes::PlaylistChanged(ev.into()),
182 UpdateEvents::Progress(ev) => StreamTypes::ProgressChanged(ev.into()),
183 };
184
185 Self { r#type: Some(val) }
186 }
187}
188
189impl TryFrom<protobuf::StreamUpdates> for UpdateEvents {
191 type Error = anyhow::Error;
192
193 fn try_from(value: protobuf::StreamUpdates) -> Result<Self, Self::Error> {
194 let value = unwrap_msg(value.r#type, "StreamUpdates.type")?;
195
196 let res = match value {
197 StreamTypes::VolumeChanged(ev) => Self::VolumeChanged {
198 volume: clamp_u16(
199 unwrap_msg(ev.msg, "StreamUpdates.types.volume_changed.msg")?.volume,
200 ),
201 },
202 StreamTypes::SpeedChanged(ev) => Self::SpeedChanged {
203 speed: unwrap_msg(ev.msg, "StreamUpdates.types.speed_changed.msg")?.speed,
204 },
205 StreamTypes::PlayStateChanged(ev) => Self::PlayStateChanged {
206 playing: unwrap_msg(ev.msg, "StreamUpdates.types.play_state_changed.msg")?.status,
207 },
208 StreamTypes::MissedEvents(ev) => Self::MissedEvents { amount: ev.amount },
209 StreamTypes::TrackChanged(ev) => Self::TrackChanged(TrackChangedInfo {
210 current_track_index: ev.current_track_index,
211 current_track_updated: ev.current_track_updated,
212 title: ev.optional_title.map(|v| {
213 let protobuf::update_track_changed::OptionalTitle::Title(v) = v;
214 v
215 }),
216 progress: ev.progress.map(Into::into),
217 }),
218 StreamTypes::GaplessChanged(ev) => Self::GaplessChanged {
219 gapless: unwrap_msg(ev.msg, "StreamUpdates.types.gapless_changed.msg")?.gapless,
220 },
221 StreamTypes::PlaylistChanged(ev) => Self::PlaylistChanged(
222 ev.try_into()
223 .context("In \"StreamUpdates.types.playlist_changed\"")?,
224 ),
225 StreamTypes::ProgressChanged(ev) => Self::Progress(
226 ev.try_into()
227 .context("In \"StreamUpdates.types.progress_changed\"")?,
228 ),
229 };
230
231 Ok(res)
232 }
233}
234
235#[derive(Debug, Clone, PartialEq)]
236pub struct PlaylistAddTrackInfo {
237 pub at_index: u64,
240 pub title: Option<String>,
241 pub duration: PlayerTimeUnit,
243 pub trackid: playlist_helpers::PlaylistTrackSource,
244}
245
246#[derive(Debug, Clone, PartialEq)]
247pub struct PlaylistRemoveTrackInfo {
248 pub at_index: u64,
250 pub trackid: playlist_helpers::PlaylistTrackSource,
252}
253
254#[derive(Debug, Clone, PartialEq)]
255pub struct PlaylistLoopModeInfo {
256 pub mode: u32,
258}
259
260impl From<LoopMode> for PlaylistLoopModeInfo {
261 fn from(value: LoopMode) -> Self {
262 Self {
263 mode: u32::from(value.discriminant()),
264 }
265 }
266}
267
268#[derive(Debug, Clone, PartialEq)]
269pub struct PlaylistSwapInfo {
270 pub index_a: u64,
271 pub index_b: u64,
272}
273
274#[derive(Debug, Clone, PartialEq)]
275pub struct PlaylistShuffledInfo {
276 pub tracks: PlaylistTracks,
277}
278
279#[derive(Debug, Clone, PartialEq)]
281pub enum UpdatePlaylistEvents {
282 PlaylistAddTrack(PlaylistAddTrackInfo),
283 PlaylistRemoveTrack(PlaylistRemoveTrackInfo),
284 PlaylistCleared,
285 PlaylistLoopMode(PlaylistLoopModeInfo),
286 PlaylistSwapTracks(PlaylistSwapInfo),
287 PlaylistShuffled(PlaylistShuffledInfo),
288}
289
290type PPlaylistTypes = protobuf::update_playlist::Type;
291
292impl From<UpdatePlaylistEvents> for protobuf::UpdatePlaylist {
294 fn from(value: UpdatePlaylistEvents) -> Self {
295 let val = match value {
296 UpdatePlaylistEvents::PlaylistAddTrack(vals) => {
297 PPlaylistTypes::AddTrack(protobuf::PlaylistAddTrack {
298 at_index: vals.at_index,
299 optional_title: vals
300 .title
301 .map(protobuf::playlist_add_track::OptionalTitle::Title),
302 duration: Some(vals.duration.into()),
303 id: Some(vals.trackid.into()),
304 })
305 }
306 UpdatePlaylistEvents::PlaylistRemoveTrack(vals) => {
307 PPlaylistTypes::RemoveTrack(protobuf::PlaylistRemoveTrack {
308 at_index: vals.at_index,
309 id: Some(vals.trackid.into()),
310 })
311 }
312 UpdatePlaylistEvents::PlaylistCleared => PPlaylistTypes::Cleared(PlaylistCleared {}),
313 UpdatePlaylistEvents::PlaylistLoopMode(vals) => {
314 PPlaylistTypes::LoopMode(PlaylistLoopMode { mode: vals.mode })
315 }
316 UpdatePlaylistEvents::PlaylistSwapTracks(vals) => {
317 PPlaylistTypes::SwapTracks(protobuf::PlaylistSwapTracks {
318 index_a: vals.index_a,
319 index_b: vals.index_b,
320 })
321 }
322 UpdatePlaylistEvents::PlaylistShuffled(vals) => {
323 PPlaylistTypes::Shuffled(protobuf::PlaylistShuffled {
324 shuffled: Some(vals.tracks),
325 })
326 }
327 };
328
329 Self { r#type: Some(val) }
330 }
331}
332
333impl TryFrom<protobuf::UpdatePlaylist> for UpdatePlaylistEvents {
335 type Error = anyhow::Error;
336
337 fn try_from(value: protobuf::UpdatePlaylist) -> Result<Self, Self::Error> {
338 let value = unwrap_msg(value.r#type, "UpdatePlaylist.type")?;
339
340 let res = match value {
341 PPlaylistTypes::AddTrack(ev) => Self::PlaylistAddTrack(PlaylistAddTrackInfo {
342 at_index: ev.at_index,
343 title: ev.optional_title.map(|v| {
344 let protobuf::playlist_add_track::OptionalTitle::Title(v) = v;
345 v
346 }),
347 duration: unwrap_msg(ev.duration, "UpdatePlaylist.type.add_track.duration")?.into(),
348 trackid: unwrap_msg(
349 unwrap_msg(ev.id, "UpdatePlaylist.type.add_track.id")?.source,
350 "UpdatePlaylist.type.add_track.id.source",
351 )?
352 .try_into()?,
353 }),
354 PPlaylistTypes::RemoveTrack(ev) => Self::PlaylistRemoveTrack(PlaylistRemoveTrackInfo {
355 at_index: ev.at_index,
356 trackid: unwrap_msg(
357 unwrap_msg(ev.id, "UpdatePlaylist.type.remove_track.id")?.source,
358 "UpdatePlaylist.type.remove_track.id.source",
359 )?
360 .try_into()?,
361 }),
362 PPlaylistTypes::Cleared(_) => Self::PlaylistCleared,
363 PPlaylistTypes::LoopMode(ev) => {
364 Self::PlaylistLoopMode(PlaylistLoopModeInfo { mode: ev.mode })
365 }
366 PPlaylistTypes::SwapTracks(ev) => Self::PlaylistSwapTracks(PlaylistSwapInfo {
367 index_a: ev.index_a,
368 index_b: ev.index_b,
369 }),
370 PPlaylistTypes::Shuffled(ev) => {
371 let shuffled = unwrap_msg(ev.shuffled, "UpdatePlaylist.type.shuffled.shuffled")?;
372 Self::PlaylistShuffled(PlaylistShuffledInfo { tracks: shuffled })
373 }
374 };
375
376 Ok(res)
377 }
378}
379
380fn unwrap_msg<T>(opt: Option<T>, place: &str) -> Result<T, anyhow::Error> {
382 match opt {
383 Some(val) => Ok(val),
384 None => Err(anyhow!("Got \"None\" in grpc \"{place}\"!")),
385 }
386}
387
388#[allow(clippy::cast_possible_truncation)]
389fn clamp_u16(val: u32) -> u16 {
390 val.min(u32::from(u16::MAX)) as u16
391}
392
393pub mod playlist_helpers {
394 use anyhow::Context;
395
396 use super::{PlaylistTracksToRemoveClear, protobuf, unwrap_msg};
397
398 #[derive(Debug, Clone, PartialEq)]
400 pub enum PlaylistTrackSource {
401 Path(String),
402 Url(String),
403 PodcastUrl(String),
404 }
405
406 impl From<PlaylistTrackSource> for protobuf::track_id::Source {
407 fn from(value: PlaylistTrackSource) -> Self {
408 match value {
409 PlaylistTrackSource::Path(v) => Self::Path(v),
410 PlaylistTrackSource::Url(v) => Self::Url(v),
411 PlaylistTrackSource::PodcastUrl(v) => Self::PodcastUrl(v),
412 }
413 }
414 }
415
416 impl From<PlaylistTrackSource> for protobuf::TrackId {
417 fn from(value: PlaylistTrackSource) -> Self {
418 Self {
419 source: Some(value.into()),
420 }
421 }
422 }
423
424 impl TryFrom<protobuf::track_id::Source> for PlaylistTrackSource {
425 type Error = anyhow::Error;
426
427 fn try_from(value: protobuf::track_id::Source) -> Result<Self, Self::Error> {
428 Ok(match value {
429 protobuf::track_id::Source::Path(v) => Self::Path(v),
430 protobuf::track_id::Source::Url(v) => Self::Url(v),
431 protobuf::track_id::Source::PodcastUrl(v) => Self::PodcastUrl(v),
432 })
433 }
434 }
435
436 impl TryFrom<protobuf::TrackId> for PlaylistTrackSource {
437 type Error = anyhow::Error;
438
439 fn try_from(value: protobuf::TrackId) -> Result<Self, Self::Error> {
440 unwrap_msg(value.source, "TrackId.source").and_then(Self::try_from)
441 }
442 }
443
444 #[derive(Debug, Clone, PartialEq)]
446 pub struct PlaylistAddTrack {
447 pub at_index: u64,
448 pub tracks: Vec<PlaylistTrackSource>,
449 }
450
451 impl PlaylistAddTrack {
452 #[must_use]
453 pub fn new_single(at_index: u64, track: PlaylistTrackSource) -> Self {
454 Self {
455 at_index,
456 tracks: vec![track],
457 }
458 }
459
460 #[must_use]
461 pub fn new_vec(at_index: u64, tracks: Vec<PlaylistTrackSource>) -> Self {
462 Self { at_index, tracks }
463 }
464 }
465
466 impl From<PlaylistAddTrack> for protobuf::PlaylistTracksToAdd {
467 fn from(value: PlaylistAddTrack) -> Self {
468 Self {
469 at_index: value.at_index,
470 tracks: value.tracks.into_iter().map(Into::into).collect(),
471 }
472 }
473 }
474
475 impl TryFrom<protobuf::PlaylistTracksToAdd> for PlaylistAddTrack {
476 type Error = anyhow::Error;
477
478 fn try_from(value: protobuf::PlaylistTracksToAdd) -> Result<Self, Self::Error> {
479 let tracks = value
480 .tracks
481 .into_iter()
482 .map(|v| PlaylistTrackSource::try_from(v).context("PlaylistTracksToAdd.tracks"))
483 .collect::<Result<Vec<_>, anyhow::Error>>()?;
484
485 Ok(Self {
486 at_index: value.at_index,
487 tracks,
488 })
489 }
490 }
491
492 #[derive(Debug, Clone, PartialEq)]
494 pub struct PlaylistRemoveTrackIndexed {
495 pub at_index: u64,
496 pub tracks: Vec<PlaylistTrackSource>,
497 }
498
499 impl PlaylistRemoveTrackIndexed {
500 #[must_use]
501 pub fn new_single(at_index: u64, track: PlaylistTrackSource) -> Self {
502 Self {
503 at_index,
504 tracks: vec![track],
505 }
506 }
507
508 #[must_use]
509 pub fn new_vec(at_index: u64, tracks: Vec<PlaylistTrackSource>) -> Self {
510 Self { at_index, tracks }
511 }
512 }
513
514 impl From<PlaylistRemoveTrackIndexed> for protobuf::PlaylistTracksToRemoveIndexed {
515 fn from(value: PlaylistRemoveTrackIndexed) -> Self {
516 Self {
517 at_index: value.at_index,
518 tracks: value.tracks.into_iter().map(Into::into).collect(),
519 }
520 }
521 }
522
523 impl TryFrom<protobuf::PlaylistTracksToRemoveIndexed> for PlaylistRemoveTrackIndexed {
524 type Error = anyhow::Error;
525
526 fn try_from(value: protobuf::PlaylistTracksToRemoveIndexed) -> Result<Self, Self::Error> {
527 let tracks = value
528 .tracks
529 .into_iter()
530 .map(|v| {
531 PlaylistTrackSource::try_from(v).context("PlaylistTracksToRemoveIndexed.tracks")
532 })
533 .collect::<Result<Vec<_>, anyhow::Error>>()?;
534
535 Ok(Self {
536 at_index: value.at_index,
537 tracks,
538 })
539 }
540 }
541
542 #[derive(Debug, Clone, PartialEq)]
544 pub enum PlaylistRemoveTrackType {
545 Indexed(PlaylistRemoveTrackIndexed),
546 Clear,
547 }
548
549 type PToRemoveTypes = protobuf::playlist_tracks_to_remove::Type;
550
551 impl From<PlaylistRemoveTrackType> for protobuf::PlaylistTracksToRemove {
552 fn from(value: PlaylistRemoveTrackType) -> Self {
553 Self {
554 r#type: Some(match value {
555 PlaylistRemoveTrackType::Indexed(v) => PToRemoveTypes::Indexed(v.into()),
556 PlaylistRemoveTrackType::Clear => {
557 PToRemoveTypes::Clear(PlaylistTracksToRemoveClear {})
558 }
559 }),
560 }
561 }
562 }
563
564 impl TryFrom<protobuf::PlaylistTracksToRemove> for PlaylistRemoveTrackType {
565 type Error = anyhow::Error;
566
567 fn try_from(value: protobuf::PlaylistTracksToRemove) -> Result<Self, Self::Error> {
568 let value = unwrap_msg(value.r#type, "PlaylistTracksToRemove.type")?;
569
570 Ok(match value {
571 PToRemoveTypes::Indexed(v) => Self::Indexed(v.try_into()?),
572 PToRemoveTypes::Clear(_) => Self::Clear,
573 })
574 }
575 }
576
577 #[derive(Debug, Clone, PartialEq)]
579 pub struct PlaylistSwapTrack {
580 pub index_a: u64,
581 pub index_b: u64,
582 }
583
584 impl From<PlaylistSwapTrack> for protobuf::PlaylistSwapTracks {
585 fn from(value: PlaylistSwapTrack) -> Self {
586 Self {
587 index_a: value.index_a,
588 index_b: value.index_b,
589 }
590 }
591 }
592
593 impl TryFrom<protobuf::PlaylistSwapTracks> for PlaylistSwapTrack {
594 type Error = anyhow::Error;
595
596 fn try_from(value: protobuf::PlaylistSwapTracks) -> Result<Self, Self::Error> {
597 Ok(Self {
598 index_a: value.index_a,
599 index_b: value.index_b,
600 })
601 }
602 }
603
604 #[derive(Debug, Clone, PartialEq)]
606 pub struct PlaylistPlaySpecific {
607 pub track_index: u64,
608 pub id: PlaylistTrackSource,
609 }
610
611 impl From<PlaylistPlaySpecific> for protobuf::PlaylistPlaySpecific {
612 fn from(value: PlaylistPlaySpecific) -> Self {
613 Self {
614 track_index: value.track_index,
615 id: Some(value.id.into()),
616 }
617 }
618 }
619
620 impl TryFrom<protobuf::PlaylistPlaySpecific> for PlaylistPlaySpecific {
621 type Error = anyhow::Error;
622
623 fn try_from(value: protobuf::PlaylistPlaySpecific) -> Result<Self, Self::Error> {
624 Ok(Self {
625 track_index: value.track_index,
626 id: unwrap_msg(value.id, "PlaylistPlaySpecific.id").and_then(|v| {
627 PlaylistTrackSource::try_from(v).context("PlaylistPlaySpecific.id")
628 })?,
629 })
630 }
631 }
632}