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)]
392#[must_use]
393pub fn clamp_u16(val: u32) -> u16 {
394 val.min(u32::from(u16::MAX)) as u16
395}
396
397pub mod playlist_helpers {
398 use anyhow::Context;
399
400 use super::{PlaylistTracksToRemoveClear, protobuf, unwrap_msg};
401
402 #[derive(Debug, Clone, PartialEq)]
404 pub enum PlaylistTrackSource {
405 Path(String),
406 Url(String),
407 PodcastUrl(String),
408 }
409
410 impl From<PlaylistTrackSource> for protobuf::track_id::Source {
411 fn from(value: PlaylistTrackSource) -> Self {
412 match value {
413 PlaylistTrackSource::Path(v) => Self::Path(v),
414 PlaylistTrackSource::Url(v) => Self::Url(v),
415 PlaylistTrackSource::PodcastUrl(v) => Self::PodcastUrl(v),
416 }
417 }
418 }
419
420 impl From<PlaylistTrackSource> for protobuf::TrackId {
421 fn from(value: PlaylistTrackSource) -> Self {
422 Self {
423 source: Some(value.into()),
424 }
425 }
426 }
427
428 impl TryFrom<protobuf::track_id::Source> for PlaylistTrackSource {
429 type Error = anyhow::Error;
430
431 fn try_from(value: protobuf::track_id::Source) -> Result<Self, Self::Error> {
432 Ok(match value {
433 protobuf::track_id::Source::Path(v) => Self::Path(v),
434 protobuf::track_id::Source::Url(v) => Self::Url(v),
435 protobuf::track_id::Source::PodcastUrl(v) => Self::PodcastUrl(v),
436 })
437 }
438 }
439
440 impl TryFrom<protobuf::TrackId> for PlaylistTrackSource {
441 type Error = anyhow::Error;
442
443 fn try_from(value: protobuf::TrackId) -> Result<Self, Self::Error> {
444 unwrap_msg(value.source, "TrackId.source").and_then(Self::try_from)
445 }
446 }
447
448 #[derive(Debug, Clone, PartialEq)]
450 pub struct PlaylistAddTrack {
451 pub at_index: u64,
452 pub tracks: Vec<PlaylistTrackSource>,
453 }
454
455 impl PlaylistAddTrack {
456 #[must_use]
457 pub fn new_single(at_index: u64, track: PlaylistTrackSource) -> Self {
458 Self {
459 at_index,
460 tracks: vec![track],
461 }
462 }
463
464 #[must_use]
465 pub fn new_vec(at_index: u64, tracks: Vec<PlaylistTrackSource>) -> Self {
466 Self { at_index, tracks }
467 }
468 }
469
470 impl From<PlaylistAddTrack> for protobuf::PlaylistTracksToAdd {
471 fn from(value: PlaylistAddTrack) -> Self {
472 Self {
473 at_index: value.at_index,
474 tracks: value.tracks.into_iter().map(Into::into).collect(),
475 }
476 }
477 }
478
479 impl TryFrom<protobuf::PlaylistTracksToAdd> for PlaylistAddTrack {
480 type Error = anyhow::Error;
481
482 fn try_from(value: protobuf::PlaylistTracksToAdd) -> Result<Self, Self::Error> {
483 let tracks = value
484 .tracks
485 .into_iter()
486 .map(|v| PlaylistTrackSource::try_from(v).context("PlaylistTracksToAdd.tracks"))
487 .collect::<Result<Vec<_>, anyhow::Error>>()?;
488
489 Ok(Self {
490 at_index: value.at_index,
491 tracks,
492 })
493 }
494 }
495
496 #[derive(Debug, Clone, PartialEq)]
498 pub struct PlaylistRemoveTrackIndexed {
499 pub at_index: u64,
500 pub tracks: Vec<PlaylistTrackSource>,
501 }
502
503 impl PlaylistRemoveTrackIndexed {
504 #[must_use]
505 pub fn new_single(at_index: u64, track: PlaylistTrackSource) -> Self {
506 Self {
507 at_index,
508 tracks: vec![track],
509 }
510 }
511
512 #[must_use]
513 pub fn new_vec(at_index: u64, tracks: Vec<PlaylistTrackSource>) -> Self {
514 Self { at_index, tracks }
515 }
516 }
517
518 impl From<PlaylistRemoveTrackIndexed> for protobuf::PlaylistTracksToRemoveIndexed {
519 fn from(value: PlaylistRemoveTrackIndexed) -> Self {
520 Self {
521 at_index: value.at_index,
522 tracks: value.tracks.into_iter().map(Into::into).collect(),
523 }
524 }
525 }
526
527 impl TryFrom<protobuf::PlaylistTracksToRemoveIndexed> for PlaylistRemoveTrackIndexed {
528 type Error = anyhow::Error;
529
530 fn try_from(value: protobuf::PlaylistTracksToRemoveIndexed) -> Result<Self, Self::Error> {
531 let tracks = value
532 .tracks
533 .into_iter()
534 .map(|v| {
535 PlaylistTrackSource::try_from(v).context("PlaylistTracksToRemoveIndexed.tracks")
536 })
537 .collect::<Result<Vec<_>, anyhow::Error>>()?;
538
539 Ok(Self {
540 at_index: value.at_index,
541 tracks,
542 })
543 }
544 }
545
546 #[derive(Debug, Clone, PartialEq)]
548 pub enum PlaylistRemoveTrackType {
549 Indexed(PlaylistRemoveTrackIndexed),
550 Clear,
551 }
552
553 type PToRemoveTypes = protobuf::playlist_tracks_to_remove::Type;
554
555 impl From<PlaylistRemoveTrackType> for protobuf::PlaylistTracksToRemove {
556 fn from(value: PlaylistRemoveTrackType) -> Self {
557 Self {
558 r#type: Some(match value {
559 PlaylistRemoveTrackType::Indexed(v) => PToRemoveTypes::Indexed(v.into()),
560 PlaylistRemoveTrackType::Clear => {
561 PToRemoveTypes::Clear(PlaylistTracksToRemoveClear {})
562 }
563 }),
564 }
565 }
566 }
567
568 impl TryFrom<protobuf::PlaylistTracksToRemove> for PlaylistRemoveTrackType {
569 type Error = anyhow::Error;
570
571 fn try_from(value: protobuf::PlaylistTracksToRemove) -> Result<Self, Self::Error> {
572 let value = unwrap_msg(value.r#type, "PlaylistTracksToRemove.type")?;
573
574 Ok(match value {
575 PToRemoveTypes::Indexed(v) => Self::Indexed(v.try_into()?),
576 PToRemoveTypes::Clear(_) => Self::Clear,
577 })
578 }
579 }
580
581 #[derive(Debug, Clone, PartialEq)]
583 pub struct PlaylistSwapTrack {
584 pub index_a: u64,
585 pub index_b: u64,
586 }
587
588 impl From<PlaylistSwapTrack> for protobuf::PlaylistSwapTracks {
589 fn from(value: PlaylistSwapTrack) -> Self {
590 Self {
591 index_a: value.index_a,
592 index_b: value.index_b,
593 }
594 }
595 }
596
597 impl TryFrom<protobuf::PlaylistSwapTracks> for PlaylistSwapTrack {
598 type Error = anyhow::Error;
599
600 fn try_from(value: protobuf::PlaylistSwapTracks) -> Result<Self, Self::Error> {
601 Ok(Self {
602 index_a: value.index_a,
603 index_b: value.index_b,
604 })
605 }
606 }
607
608 #[derive(Debug, Clone, PartialEq)]
610 pub struct PlaylistPlaySpecific {
611 pub track_index: u64,
612 pub id: PlaylistTrackSource,
613 }
614
615 impl From<PlaylistPlaySpecific> for protobuf::PlaylistPlaySpecific {
616 fn from(value: PlaylistPlaySpecific) -> Self {
617 Self {
618 track_index: value.track_index,
619 id: Some(value.id.into()),
620 }
621 }
622 }
623
624 impl TryFrom<protobuf::PlaylistPlaySpecific> for PlaylistPlaySpecific {
625 type Error = anyhow::Error;
626
627 fn try_from(value: protobuf::PlaylistPlaySpecific) -> Result<Self, Self::Error> {
628 Ok(Self {
629 track_index: value.track_index,
630 id: unwrap_msg(value.id, "PlaylistPlaySpecific.id").and_then(|v| {
631 PlaylistTrackSource::try_from(v).context("PlaylistPlaySpecific.id")
632 })?,
633 })
634 }
635 }
636}