5ce4588c80
The Spotify AP reset the connection (recv -1, errno 104 ECONNRESET) on the third Ping, every 6 minutes, like clockwork. cspot answered each Ping at once; librespot (core/src/session.rs) waits 60 s: Ping -> 60 s -> Pong -> PongAck -> 60 s -> Ping. - 0003-delayed-pong: record the Ping, send the Pong 60 s later from triggerTimeout() (called on every 3 s receive timeout), through the PR #3 connection snapshot; dropped on reconnect. - 0002-diag-log-recv-errors: log recv's return value/errno before "Error in read" (error path only); this is what showed the RST. Verified: over 8+ minutes Ping/delayed Pong/PongAck every 2 min, no reset at the 6-minute mark. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
75 lines
2.5 KiB
Diff
75 lines
2.5 KiB
Diff
Keep-alive like librespot (core/src/session.rs): the server sends a Ping every ~2 min; send
|
|
the Pong 60 s later, not at once. With an immediate Pong the server reset the AP connection
|
|
(ECONNRESET) on the third Ping, i.e. every 6 minutes, and the Spotify app lost the device.
|
|
|
|
--- a/cspot/include/MercurySession.h
|
|
+++ b/cspot/include/MercurySession.h
|
|
@@ -120,6 +120,14 @@
|
|
|
|
unsigned long long timestampDiff;
|
|
unsigned long long lastPingTimestamp = -1;
|
|
+
|
|
+ // Keep-alive as librespot does it: Ping -> wait 60 s -> Pong -> PongAck. Answering at once made
|
|
+ // the server reset the connection on the third Ping (every 6 minutes).
|
|
+ const int PONG_DELAY_MS = 60 * 1000;
|
|
+ bool pongPending = false;
|
|
+ unsigned long long pongDueTimestamp = 0;
|
|
+ std::vector<uint8_t> pongData;
|
|
+ void sendPendingPong();
|
|
std::string countryCode = "";
|
|
|
|
std::mutex isRunningMutex;
|
|
--- a/cspot/src/MercurySession.cpp
|
|
+++ b/cspot/src/MercurySession.cpp
|
|
@@ -45,7 +45,10 @@
|
|
timeProvider->syncWithPingPacket(packet.data);
|
|
|
|
this->lastPingTimestamp = timeProvider->getSyncedTimestamp();
|
|
- this->shanConn->sendPacket(0x49, packet.data);
|
|
+ // Pong 60 s later (sent from triggerTimeout(), called on every 3 s receive timeout)
|
|
+ this->pongData = packet.data;
|
|
+ this->pongDueTimestamp = this->lastPingTimestamp + PONG_DELAY_MS;
|
|
+ this->pongPending = true;
|
|
} else {
|
|
this->packetQueue.push(packet);
|
|
}
|
|
@@ -80,6 +83,7 @@
|
|
BELL_SLEEP_MS(100);
|
|
|
|
lastPingTimestamp = timeProvider->getSyncedTimestamp();
|
|
+ pongPending = false; // a Ping on the old connection needs no Pong on the new one
|
|
isReconnecting = false;
|
|
|
|
this->executeEstabilishedCallback = true;
|
|
@@ -98,9 +102,30 @@
|
|
this->connectionReadyCallback = callback;
|
|
}
|
|
|
|
+void MercurySession::sendPendingPong() {
|
|
+ if (!pongPending ||
|
|
+ timeProvider->getSyncedTimestamp() < pongDueTimestamp)
|
|
+ return;
|
|
+ pongPending = false;
|
|
+ std::shared_ptr<ShannonConnection> shanConn;
|
|
+ {
|
|
+ std::scoped_lock lock(connMutex);
|
|
+ shanConn = this->shanConn;
|
|
+ }
|
|
+ if (!shanConn)
|
|
+ return;
|
|
+ try {
|
|
+ shanConn->sendPacket(0x49, pongData);
|
|
+ CSPOT_LOG(info, "Sent delayed Pong");
|
|
+ } catch (...) {
|
|
+ CSPOT_LOG(error, "Failed to send Pong");
|
|
+ }
|
|
+}
|
|
+
|
|
bool MercurySession::triggerTimeout() {
|
|
if (!isRunning)
|
|
return true;
|
|
+ sendPendingPong();
|
|
auto currentTimestamp = timeProvider->getSyncedTimestamp();
|
|
|
|
if (currentTimestamp - this->lastPingTimestamp > PING_TIMEOUT_MS) {
|