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