Files
squeezelite-esp32/components/spotify/cspot/include/MercurySession.h
2023-10-02 19:06:59 -07:00

135 lines
4.2 KiB
C++

#pragma once
#include <atomic> // for atomic
#include <cstdint> // for uint8_t, uint64_t, uint32_t
#include <functional> // for function
#include <memory> // for shared_ptr
#include <mutex> // for mutex
#include <string> // for string
#include <unordered_map> // for unordered_map
#include <vector> // for vector
#include "BellTask.h" // for Task
#include "Packet.h" // for Packet
#include "Queue.h" // for Queue
#include "Session.h" // for Session
#include "protobuf/mercury.pb.h" // for Header
namespace cspot {
class TimeProvider;
class MercurySession : public bell::Task, public cspot::Session {
public:
MercurySession(std::shared_ptr<cspot::TimeProvider> timeProvider);
~MercurySession();
typedef std::vector<std::vector<uint8_t>> DataParts;
struct Response {
Header mercuryHeader;
uint8_t flags;
DataParts parts;
uint64_t sequenceId;
bool fail;
};
typedef std::function<void(Response&)> ResponseCallback;
typedef std::function<void(bool, const std::vector<uint8_t>&)>
AudioKeyCallback;
typedef std::function<void()> ConnectionEstabilishedCallback;
enum class RequestType : uint8_t {
SUB = 0xb3,
UNSUB = 0xb4,
SUBRES = 0xb5,
SEND = 0xb2,
GET = 0xFF, // Shitty workaround, it's value is actually same as SEND
PING = 0x04,
PONG_ACK = 0x4a,
AUDIO_CHUNK_REQUEST_COMMAND = 0x08,
AUDIO_CHUNK_SUCCESS_RESPONSE = 0x09,
AUDIO_CHUNK_FAILURE_RESPONSE = 0x0A,
AUDIO_KEY_REQUEST_COMMAND = 0x0C,
AUDIO_KEY_SUCCESS_RESPONSE = 0x0D,
AUDIO_KEY_FAILURE_RESPONSE = 0x0E,
COUNTRY_CODE_RESPONSE = 0x1B,
};
std::unordered_map<RequestType, std::string> RequestTypeMap = {
{RequestType::GET, "GET"},
{RequestType::SEND, "SEND"},
{RequestType::SUB, "SUB"},
{RequestType::UNSUB, "UNSUB"},
};
void handlePacket();
uint64_t executeSubscription(RequestType type, const std::string& uri,
ResponseCallback callback,
ResponseCallback subscription, DataParts& parts);
uint64_t executeSubscription(RequestType type, const std::string& uri,
ResponseCallback callback,
ResponseCallback subscription) {
DataParts parts = {};
return this->executeSubscription(type, uri, callback, subscription, parts);
}
uint64_t execute(RequestType type, const std::string& uri,
ResponseCallback callback) {
return this->executeSubscription(type, uri, callback, nullptr);
}
uint64_t execute(RequestType type, const std::string& uri,
ResponseCallback callback, DataParts& parts) {
return this->executeSubscription(type, uri, callback, nullptr, parts);
}
void unregister(uint64_t sequenceId);
void unregisterAudioKey(uint32_t sequenceId);
uint32_t requestAudioKey(const std::vector<uint8_t>& trackId,
const std::vector<uint8_t>& fileId,
AudioKeyCallback audioCallback);
std::string getCountryCode();
void disconnect();
void setConnectedHandler(ConnectionEstabilishedCallback callback);
bool triggerTimeout() override;
private:
const int PING_TIMEOUT_MS = 2 * 60 * 1000 + 5000;
std::shared_ptr<cspot::TimeProvider> timeProvider;
Header tempMercuryHeader = {};
ConnectionEstabilishedCallback connectionReadyCallback = nullptr;
bell::Queue<cspot::Packet> packetQueue;
void runTask() override;
void reconnect();
std::unordered_map<uint64_t, ResponseCallback> callbacks;
std::unordered_map<std::string, ResponseCallback> subscriptions;
std::unordered_map<uint32_t, AudioKeyCallback> audioKeyCallbacks;
uint64_t sequenceId = 1;
uint32_t audioKeySequence = 1;
unsigned long long timestampDiff;
unsigned long long lastPingTimestamp = -1;
std::string countryCode = "";
std::mutex isRunningMutex;
std::atomic<bool> isRunning = false;
std::atomic<bool> isReconnecting = false;
std::atomic<bool> executeEstabilishedCallback = false;
void failAllPending();
Response decodeResponse(const std::vector<uint8_t>& data);
};
} // namespace cspot