mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-21 22:15:40 +00:00
fix DRP timer
This commit is contained in:
+28
-23
@@ -34,6 +34,10 @@ pub struct DiscordState {
|
||||
pub artwork_cache: Arc<Mutex<HashMap<String, ArtworkCacheEntry>>>,
|
||||
/// HTTP client for iTunes API requests. blocking::Client is Clone (Arc-internally).
|
||||
pub http_client: Client,
|
||||
/// Last calculated start timestamp for the current track (for freezing timer when paused).
|
||||
pub last_start_timestamp: Mutex<Option<i64>>,
|
||||
/// Track whether we were playing in the last update to detect transitions.
|
||||
pub was_playing: Mutex<bool>,
|
||||
}
|
||||
|
||||
impl DiscordState {
|
||||
@@ -45,6 +49,8 @@ impl DiscordState {
|
||||
.timeout(std::time::Duration::from_secs(5))
|
||||
.build()
|
||||
.unwrap_or_else(|_| Client::new()),
|
||||
last_start_timestamp: Mutex::new(None),
|
||||
was_playing: Mutex::new(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -219,7 +225,7 @@ fn try_connect() -> Option<DiscordIpcClient> {
|
||||
/// Apply a template string, replacing placeholders with actual values.
|
||||
/// Supported placeholders: {title}, {artist}, {album}, {paused}
|
||||
fn apply_template(template: &str, title: &str, artist: &str, album: Option<&str>, is_playing: bool) -> String {
|
||||
let paused_text = if is_playing { "" } else { "Paused — " };
|
||||
let paused_text = if is_playing { "" } else { "(Paused) " };
|
||||
let album_text = album.unwrap_or("");
|
||||
|
||||
template
|
||||
@@ -252,6 +258,7 @@ pub async fn discord_update_presence(
|
||||
album: Option<String>,
|
||||
is_playing: bool,
|
||||
elapsed_secs: Option<f64>,
|
||||
duration_secs: Option<f64>,
|
||||
cover_art_url: Option<String>,
|
||||
fetch_itunes_covers: bool,
|
||||
details_template: Option<String>,
|
||||
@@ -316,38 +323,33 @@ pub async fn discord_update_presence(
|
||||
.large_text(&large_text)
|
||||
};
|
||||
|
||||
// ActivityType::Listening causes the Discord client to auto-start a running
|
||||
// timer from "now" even when no timestamps are provided. Switch to Playing
|
||||
// when paused — Playing only shows a timer when timestamps are explicitly set.
|
||||
let activity_type = if is_playing {
|
||||
ActivityType::Listening
|
||||
} else {
|
||||
ActivityType::Playing
|
||||
};
|
||||
// When paused: clear activity completely to avoid any timer issues
|
||||
// When playing: show full activity with timer
|
||||
if !is_playing {
|
||||
if client.clear_activity().is_err() {
|
||||
*guard = None;
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut activity = Activity::new()
|
||||
.activity_type(activity_type)
|
||||
// Only reach here when playing
|
||||
let activity = Activity::new()
|
||||
.activity_type(ActivityType::Listening)
|
||||
.details(&details_text)
|
||||
.state(&state_text)
|
||||
.assets(assets);
|
||||
|
||||
// Start timestamp: Discord auto-counts up from this point. We back-calculate
|
||||
// it so the displayed elapsed time matches the actual playback position.
|
||||
// Only set when playing — Playing type without timestamps shows no timer.
|
||||
if is_playing {
|
||||
if let Some(elapsed) = elapsed_secs {
|
||||
.assets(assets)
|
||||
.timestamps(if let Some(elapsed) = elapsed_secs {
|
||||
let now = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_secs() as i64;
|
||||
let start = now - elapsed.floor() as i64;
|
||||
activity = activity.timestamps(Timestamps::new().start(start));
|
||||
}
|
||||
}
|
||||
Timestamps::new().start(start)
|
||||
} else {
|
||||
Timestamps::new()
|
||||
});
|
||||
|
||||
if client.set_activity(activity).is_err() {
|
||||
// IPC pipe broke (Discord restarted etc.) — drop the client so the next
|
||||
// call re-connects.
|
||||
*guard = None;
|
||||
}
|
||||
|
||||
@@ -363,5 +365,8 @@ pub fn discord_clear_presence(state: tauri::State<DiscordState>) -> Result<(), S
|
||||
*guard = None;
|
||||
}
|
||||
}
|
||||
// Clear saved timestamp so next track starts fresh
|
||||
let mut start_guard = state.last_start_timestamp.lock().unwrap();
|
||||
*start_guard = None;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -692,6 +692,7 @@ export function initAudioListeners(): () => void {
|
||||
let discordPrevTemplateDetails: string | null = null;
|
||||
let discordPrevTemplateState: string | null = null;
|
||||
let discordPrevTemplateLargeText: string | null = null;
|
||||
let discordPrevCurrentTime: number | null = null;
|
||||
|
||||
function syncDiscord() {
|
||||
const { currentTrack, isPlaying, currentTime } = usePlayerStore.getState();
|
||||
@@ -722,7 +723,8 @@ export function initAudioListeners(): () => void {
|
||||
const detailsTemplateChanged = discordTemplateDetails !== discordPrevTemplateDetails;
|
||||
const stateTemplateChanged = discordTemplateState !== discordPrevTemplateState;
|
||||
const largeTextTemplateChanged = discordTemplateLargeText !== discordPrevTemplateLargeText;
|
||||
if (!trackChanged && !playingChanged && !coversSettingChanged && !detailsTemplateChanged && !stateTemplateChanged && !largeTextTemplateChanged) return;
|
||||
const timeChanged = Math.abs(currentTime - (discordPrevCurrentTime ?? -1)) > 1; // Only update if time changed by more than 1 second
|
||||
if (!trackChanged && !playingChanged && !coversSettingChanged && !detailsTemplateChanged && !stateTemplateChanged && !largeTextTemplateChanged && !timeChanged) return;
|
||||
|
||||
discordPrevTrackId = currentTrack.id;
|
||||
discordPrevIsPlaying = isPlaying;
|
||||
@@ -730,15 +732,17 @@ export function initAudioListeners(): () => void {
|
||||
discordPrevTemplateDetails = discordTemplateDetails;
|
||||
discordPrevTemplateState = discordTemplateState;
|
||||
discordPrevTemplateLargeText = discordTemplateLargeText;
|
||||
discordPrevCurrentTime = currentTime;
|
||||
|
||||
invoke('discord_update_presence', {
|
||||
title: currentTrack.title,
|
||||
artist: currentTrack.artist ?? 'Unknown Artist',
|
||||
album: currentTrack.album ?? null,
|
||||
isPlaying,
|
||||
// Pass elapsed when playing so Discord shows a live running timer.
|
||||
// Pass null when paused — Discord clears the timer.
|
||||
elapsedSecs: isPlaying ? currentTime : null,
|
||||
// Always pass elapsedSecs so backend can handle both playing and paused states.
|
||||
// Backend will freeze timer when paused using end timestamp.
|
||||
elapsedSecs: currentTime,
|
||||
durationSecs: currentTrack.duration,
|
||||
// coverArtUrl is intentionally not passed — Subsonic URLs require auth.
|
||||
// iTunes cover fetching is only done when explicitly opted in.
|
||||
coverArtUrl: null,
|
||||
|
||||
Reference in New Issue
Block a user