use std::sync::Arc;
use mineral_protocol::{
DownloadDetailDelta, PlayerVersions, SessionMessage, SubscriptionId, UpdateEnvelope,
UpdatePayload, fragment_download_detail, fragment_player_update,
};
use tokio::sync::{Notify, broadcast, mpsc, watch};
use crate::client::ClientHandle;
pub(super) async fn pump_player(
client: ClientHandle,
id: SubscriptionId,
mut known: PlayerVersions,
resync: Arc<Notify>,
out: mpsc::Sender<SessionMessage>,
) {
let mut changes = client.state_changes();
let mut version = 0_u64;
loop {
let sync = client.player_sync(known);
known = sync.versions;
version = version.saturating_add(1);
for message in fragment_player_update(id, version, sync) {
if out.send(message).await.is_err() {
return;
}
}
tokio::select! {
changed = changes.changed() => {
if changed.is_err() {
return;
}
}
() = resync.notified() => {
known = PlayerVersions::default();
version = 0;
}
}
}
}
async fn pump_watch<T, F>(
mut rx: watch::Receiver<T>,
id: SubscriptionId,
out: mpsc::Sender<SessionMessage>,
wrap: F,
) where
T: Clone + Send + Sync + 'static,
F: Fn(T) -> UpdatePayload + Send + 'static,
{
let mut version = 0_u64;
loop {
let value = rx.borrow_and_update().clone();
version = version.saturating_add(1);
let message = SessionMessage::Update(UpdateEnvelope {
subscription: id,
version,
parts: 1,
index: 0,
payload: wrap(value),
});
if out.send(message).await.is_err() {
return;
}
if rx.changed().await.is_err() {
return;
}
}
}
pub(super) async fn pump_playback(
rx: watch::Receiver<Arc<mineral_audio::AudioSnapshot>>,
id: SubscriptionId,
out: mpsc::Sender<SessionMessage>,
) {
pump_watch(rx, id, out, |snapshot| {
UpdatePayload::Playback(Box::new(snapshot.as_ref().clone()))
})
.await;
}
pub(super) async fn pump_tasks(
rx: watch::Receiver<Arc<mineral_task::Snapshot>>,
id: SubscriptionId,
out: mpsc::Sender<SessionMessage>,
) {
pump_watch(rx, id, out, |tasks| {
UpdatePayload::Tasks(Box::new(tasks.as_ref().clone()))
})
.await;
}
pub(super) async fn pump_downloads_summary(
rx: watch::Receiver<Arc<mineral_protocol::DownloadSummary>>,
id: SubscriptionId,
out: mpsc::Sender<SessionMessage>,
) {
pump_watch(rx, id, out, |summary| {
UpdatePayload::DownloadsSummary(summary.as_ref().clone())
})
.await;
}
pub(super) async fn pump_downloads_detail(
mut snapshot: watch::Receiver<Option<Arc<Vec<mineral_protocol::SongDownloadView>>>>,
mut deltas: broadcast::Receiver<Arc<DownloadDetailDelta>>,
id: SubscriptionId,
resync: Arc<Notify>,
out: mpsc::Sender<SessionMessage>,
) {
let mut version = 0_u64;
loop {
let rows = snapshot
.borrow_and_update()
.as_ref()
.map_or_else(Vec::new, |rows| rows.as_ref().clone());
version = version.saturating_add(1);
for message in fragment_download_detail(id, version, rows) {
if out.send(message).await.is_err() {
return;
}
}
let reason = tokio::select! {
result = deltas.recv() => match result {
Ok(delta) => {
version = version.saturating_add(1);
let message = SessionMessage::Update(UpdateEnvelope {
subscription: id,
version,
parts: 1,
index: 0,
payload: UpdatePayload::DownloadsDetailDelta(delta.as_ref().clone()),
});
if out.send(message).await.is_err() {
return;
}
None
}
Err(broadcast::error::RecvError::Lagged(_skipped)) => Some(ResyncSource::Daemon),
Err(broadcast::error::RecvError::Closed) => return,
},
changed = snapshot.changed() => {
if changed.is_err() { return; }
Some(ResyncSource::Daemon)
}
() = resync.notified() => Some(ResyncSource::Client),
};
if let Some(source) = reason {
if matches!(source, ResyncSource::Client) {
version = 0;
}
continue;
}
}
}
#[derive(Clone, Copy)]
enum ResyncSource {
Client,
Daemon,
}
pub(super) async fn pump_pcm(
mut rx: mpsc::Receiver<mineral_protocol::PcmChunk>,
id: SubscriptionId,
out: mpsc::Sender<SessionMessage>,
) {
let mut version = 0_u64;
while let Some(chunk) = rx.recv().await {
version = version.saturating_add(1);
let message = SessionMessage::Update(UpdateEnvelope {
subscription: id,
version,
parts: 1,
index: 0,
payload: UpdatePayload::Pcm(chunk),
});
if out.send(message).await.is_err() {
return;
}
}
}
pub(super) async fn pump_events(
mut rx: broadcast::Receiver<mineral_protocol::Event>,
category: mineral_protocol::Subscription,
id: SubscriptionId,
client: ClientHandle,
out: mpsc::Sender<SessionMessage>,
) {
let mut version = 0_u64;
let replay = client.replay_frames(&[category]).await;
if send_events(&replay, id, &mut version, &out).await.is_err() {
return;
}
loop {
match rx.recv().await {
Ok(event) => {
if event.subscription() != category {
continue;
}
if send_events(&[event], id, &mut version, &out).await.is_err() {
return;
}
}
Err(broadcast::error::RecvError::Lagged(skipped)) => {
mineral_log::warn!(
target: "ipc",
category = ?category,
skipped,
"事件积压,尝试补发当前快照"
);
let replay = client.replay_frames(&[category]).await;
if send_events(&replay, id, &mut version, &out).await.is_err() {
return;
}
}
Err(broadcast::error::RecvError::Closed) => return,
}
}
}
async fn send_events(
events: &[mineral_protocol::Event],
id: SubscriptionId,
version: &mut u64,
out: &mpsc::Sender<SessionMessage>,
) -> Result<(), mpsc::error::SendError<SessionMessage>> {
for event in events {
*version = version.saturating_add(1);
let message = SessionMessage::Update(UpdateEnvelope {
subscription: id,
version: *version,
parts: 1,
index: 0,
payload: UpdatePayload::Event(Box::new(event.clone())),
});
out.send(message).await?;
}
Ok(())
}