mecomp_tui/state/
audio.rs1use std::{sync::Arc, time::Duration};
7
8use tokio::sync::{
9 broadcast,
10 mpsc::{UnboundedReceiver, UnboundedSender, unbounded_channel},
11};
12
13use mecomp_core::state::{Percent, StateAudio};
14use mecomp_core::{rpc::MusicPlayerClient, udp::StateChange};
15
16use crate::termination::Interrupted;
17
18use super::action::{AudioAction, PlaybackAction, QueueAction, VolumeAction};
19
20pub const TICK_RATE: Duration = Duration::from_millis(100);
21
22#[derive(Debug, Clone)]
24#[allow(clippy::module_name_repetitions)]
25pub struct AudioState {
26 state_tx: UnboundedSender<StateAudio>,
27}
28
29impl AudioState {
30 #[must_use]
32 pub fn new() -> (Self, UnboundedReceiver<StateAudio>) {
33 let (state_tx, state_rx) = unbounded_channel::<StateAudio>();
34
35 (Self { state_tx }, state_rx)
36 }
37
38 pub async fn main_loop(
44 &self,
45 daemon: Arc<MusicPlayerClient>,
46 mut action_rx: UnboundedReceiver<AudioAction>,
47 mut interrupt_rx: broadcast::Receiver<Interrupted>,
48 ) -> anyhow::Result<Interrupted> {
49 let mut state = get_state(daemon.clone()).await?;
50 let mut update_needed = false;
51
52 self.state_tx.send(state.clone())?;
54
55 let mut ticker = tokio::time::interval(TICK_RATE);
57 ticker.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
58
59 let mut update_ticker = tokio::time::interval(Duration::from_secs(1));
60 ticker.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
61
62 let result = loop {
63 tokio::select! {
64 Some(action) = action_rx.recv() => {
67 match action {
68 AudioAction::Playback(action) => handle_playback(&daemon, action).await?,
69 AudioAction::Queue(action) => handle_queue(&daemon, action).await?,
70 AudioAction::StateChange(state_change) => {
71 match state_change {
72 StateChange::Muted => state.muted = true,
73 StateChange::Unmuted => state.muted = false,
74 StateChange::VolumeChanged(volume) => state.volume = volume,
75 StateChange::TrackChanged(_) => {
76 update_needed = true;
78 },
79 StateChange::RepeatModeChanged(repeat_mode) => state.repeat_mode = repeat_mode,
80 StateChange::Seeked(seek_position) => if let Some(runtime) = &mut state.runtime {
81 runtime.seek_percent =
82 Percent::new(seek_position.as_secs_f32() / runtime.duration.as_secs_f32() * 100.0);
83 runtime.seek_position = seek_position;
84 },
85 StateChange::StatusChanged(status) => state.status = status,
86 }
87 }
88 }
89 },
90 _ = ticker.tick() => {
92 if state.paused() {
93 continue;
94 }
95 if let Some(runtime) = &mut state.runtime {
96 runtime.seek_position+= TICK_RATE;
97 runtime.seek_percent =
98 Percent::new(runtime.seek_position.as_secs_f32() / runtime.duration.as_secs_f32() * 100.0);
99 }
100 },
101 _ = update_ticker.tick() => {
103 update_needed = true;
104 },
105 Ok(interrupted) = interrupt_rx.recv() => {
107 break interrupted;
108 }
109 }
110 if update_needed {
111 state = get_state(daemon.clone()).await?;
112 update_needed = false;
113 }
114 self.state_tx.send(state.clone())?;
115 };
116
117 Ok(result)
118 }
119}
120
121async fn get_state(daemon: Arc<MusicPlayerClient>) -> anyhow::Result<StateAudio> {
123 let ctx = tarpc::context::current();
124 Ok(daemon.state_audio(ctx).await?.unwrap_or_default())
125}
126
127async fn handle_playback(daemon: &MusicPlayerClient, action: PlaybackAction) -> anyhow::Result<()> {
129 let ctx = tarpc::context::current();
130
131 match action {
132 PlaybackAction::Toggle => daemon.playback_toggle(ctx).await?,
133 PlaybackAction::Next => daemon.playback_skip_forward(ctx, 1).await?,
134 PlaybackAction::Previous => daemon.playback_skip_backward(ctx, 1).await?,
135 PlaybackAction::Seek(seek_type, duration) => {
136 daemon.playback_seek(ctx, seek_type, duration).await?;
137 }
138 PlaybackAction::Volume(VolumeAction::Increase(amount)) => {
139 daemon.playback_volume_up(ctx, amount).await?;
140 }
141 PlaybackAction::Volume(VolumeAction::Decrease(amount)) => {
142 daemon.playback_volume_down(ctx, amount).await?;
143 }
144 PlaybackAction::ToggleMute => daemon.playback_volume_toggle_mute(ctx).await?,
145 }
146
147 Ok(())
148}
149
150async fn handle_queue(daemon: &MusicPlayerClient, action: QueueAction) -> anyhow::Result<()> {
152 let ctx = tarpc::context::current();
153
154 match action {
155 QueueAction::Add(ids) => daemon.queue_add_list(ctx, ids).await??,
156 QueueAction::Remove(index) => {
157 #[allow(clippy::range_plus_one)]
158 daemon.queue_remove_range(ctx, index..index + 1).await?;
159 }
160 QueueAction::SetPosition(index) => daemon.queue_set_index(ctx, index).await?,
161 QueueAction::Shuffle => daemon.playback_shuffle(ctx).await?,
162 QueueAction::Clear => daemon.playback_clear(ctx).await?,
163 QueueAction::SetRepeatMode(mode) => daemon.playback_repeat(ctx, mode).await?,
164 }
165
166 Ok(())
167}