浏览代码

update CSpot + clear audio buffer when changing track

philippe44 2 年之前
父节点
当前提交
0222dbd6de

+ 167 - 1
components/spotify/cspot/bell/include/BiquadFilter.h

@@ -17,6 +17,59 @@ private:
 public:
     BiquadFilter(){};
 
+    // Generates coefficients for a high pass biquad filter
+    void generateHighPassCoEffs(float f, float q){
+        if (q <= 0.0001) {
+            q = 0.0001;
+        }
+        float Fs = 1;
+
+        float w0 = 2 * M_PI * f / Fs;
+        float c = cosf(w0);
+        float s = sinf(w0);
+        float alpha = s / (2 * q);
+
+        float b0 = (1 + c) / 2;
+        float b1 = -(1 + c);
+        float b2 = b0;
+        float a0 = 1 + alpha;
+        float a1 = -2 * c;
+        float a2 = 1 - alpha;
+
+        coeffs[0] = b0 / a0;
+        coeffs[1] = b1 / a0;
+        coeffs[2] = b2 / a0;
+        coeffs[3] = a1 / a0;
+        coeffs[4] = a2 / a0;
+    }
+
+    // Generates coefficients for a low pass biquad filter
+    void generateLowPassCoEffs(float f, float q){
+        if (q <= 0.0001) {
+            q = 0.0001;
+        }
+        float Fs = 1;
+
+        float w0 = 2 * M_PI * f / Fs;
+        float c = cosf(w0);
+        float s = sinf(w0);
+        float alpha = s / (2 * q);
+
+        float b0 = (1 - c) / 2;
+        float b1 = 1 - c;
+        float b2 = b0;
+        float a0 = 1 + alpha;
+        float a1 = -2 * c;
+        float a2 = 1 - alpha;
+
+        coeffs[0] = b0 / a0;
+        coeffs[1] = b1 / a0;
+        coeffs[2] = b2 / a0;
+        coeffs[3] = a1 / a0;
+        coeffs[4] = a2 / a0;
+    }
+
+    // Generates coefficients for a high shelf biquad filter
     void generateHighShelfCoEffs(float f, float gain, float q)
     {
         if (q <= 0.0001)
@@ -106,6 +159,119 @@ public:
         coeffs[4] = a2 / a0;
     }
 
+    // Generates coefficients for a peaking biquad filter
+    void generatePeakCoEffs(float f, float gain, float q)
+    {
+        if (q <= 0.0001) {
+            q = 0.0001;
+        }
+        float Fs = 1;
+
+        float w0 = 2 * M_PI * f / Fs;
+        float c = cosf(w0);
+        float s = sinf(w0);
+        float alpha = s / (2 * q);
+
+        float b0 = alpha;
+        float b1 = 0;
+        float b2 = -alpha;
+        float a0 = 1 + alpha;
+        float a1 = -2 * c;
+        float a2 = 1 - alpha;
+
+        coeffs[0] = b0 / a0;
+        coeffs[1] = b1 / a0;
+        coeffs[2] = b2 / a0;
+        coeffs[3] = a1 / a0;
+        coeffs[4] = a2 / a0;
+    }
+
+
+    // Generates coefficients for a peaking EQ biquad filter
+    void generatePeakingEqCoEffs(float f, float gain, float q)
+    {
+        // formular taken from: https://webaudio.github.io/Audio-EQ-Cookbook/audio-eq-cookbook.html
+        if (q <= 0.0001) {
+            q = 0.0001;
+        }
+        float Fs = 1;
+
+        float w0 = 2 * M_PI * f / Fs;
+        float c = cosf(w0);
+        float s = sinf(w0);
+        float alpha = s / (2 * q);
+        float A =  sqrtf(pow(10, (double)gain / 20.0));
+
+        float b0 = 1 + (alpha*A);
+        float b1 = (-2*c);
+        float b2 = 1 - (alpha*A);
+        float a0 = 1 + (alpha/A);
+        float a1 = b1;
+        float a2 = 1 - (alpha/A);
+
+        coeffs[0] = b0 / a0;
+        coeffs[1] = b1 / a0;
+        coeffs[2] = b2 / a0;
+        coeffs[3] = a1 / a0;
+        coeffs[4] = a2 / a0;
+    }
+
+
+
+    // Generates coefficients for an all pass 180° biquad filter
+    void generateAllPass180CoEffs(float f,  float q)
+    {
+        if (q <= 0.0001) {
+            q = 0.0001;
+        }
+        float Fs = 1;
+
+        float w0 = 2 * M_PI * f / Fs;
+        float c = cosf(w0);
+        float s = sinf(w0);
+        float alpha = s / (2 * q);
+
+        float b0 = 1 - alpha;
+        float b1 = -2 * c;
+        float b2 = 1 + alpha;
+        float a0 = 1 + alpha;
+        float a1 = -2 * c;
+        float a2 = 1 - alpha;
+
+        coeffs[0] = b0 / a0;
+        coeffs[1] = b1 / a0;
+        coeffs[2] = b2 / a0;
+        coeffs[3] = a1 / a0;
+        coeffs[4] = a2 / a0;
+    }
+
+    // Generates coefficients for an all pass 360° biquad filter
+    void generateAllPass360CoEffs(float f,  float q)
+    {
+        if (q <= 0.0001) {
+            q = 0.0001;
+        }
+        float Fs = 1;
+
+        float w0 = 2 * M_PI * f / Fs;
+        float c = cosf(w0);
+        float s = sinf(w0);
+        float alpha = s / (2 * q);
+
+        float b0 = 1 - alpha;
+        float b1 = -2 * c;
+        float b2 = 1 + alpha;
+        float a0 = 1 + alpha;
+        float a1 = -2 * c;
+        float a2 = 1 - alpha;
+
+        coeffs[0] = b0 / a0;
+        coeffs[1] = b1 / a0;
+        coeffs[2] = b2 / a0;
+        coeffs[3] = a1 / a0;
+        coeffs[4] = a2 / a0;
+    }
+
     void processSamples(float *input, int numSamples)
     {
         std::scoped_lock lock(processMutex);
@@ -125,4 +291,4 @@ public:
     }
 };
 
-#endif
+#endif

+ 1 - 1
components/spotify/cspot/bell/src/BinaryReader.cpp

@@ -67,6 +67,6 @@ long long bell::BinaryReader::readLong() {
     long low = readInt();
 
     return static_cast<long long>(
-        (high << 32) | low );
+        ((long long) high << 32) | low );
 }
 

+ 1 - 0
components/spotify/cspot/bell/src/CryptoOpenSSL.cpp

@@ -127,6 +127,7 @@ void CryptoOpenSSL::aesECBdecrypt(const std::vector<uint8_t>& key, std::vector<u
     int len = 0;
 
     EVP_DecryptInit_ex(ctx, EVP_aes_192_ecb(), NULL, key.data(), NULL);
+    EVP_CIPHER_CTX_set_padding(ctx, 0); // disable padding
     EVP_DecryptUpdate(ctx, data.data(), &len, data.data(), data.size());
     EVP_DecryptFinal_ex(ctx, data.data() + len, &len);
 

+ 21 - 9
components/spotify/cspot/bell/src/HTTPServer.cpp

@@ -1,4 +1,5 @@
 #include "HTTPServer.h"
+#include <cstring>
 
 bell::HTTPServer::HTTPServer(int serverPort) { this->serverPort = serverPort; }
 
@@ -65,8 +66,7 @@ void bell::HTTPServer::registerHandler(RequestType requestType,
 }
 
 void bell::HTTPServer::listen() {
-    BELL_LOG(info, "http", "Starting server at port %d",
-             this->serverPort);
+    BELL_LOG(info, "http", "Starting server at port %d", this->serverPort);
 
     // setup address
     struct addrinfo hints, *server;
@@ -82,9 +82,20 @@ void bell::HTTPServer::listen() {
     socklen_t incomingSockSize;
     int i;
     int yes = true;
-    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
-    bind(sockfd, server->ai_addr, server->ai_addrlen);
-    ::listen(sockfd, 10);
+    if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) < 0) {
+        throw std::runtime_error("setsockopt failed: " +
+                                 std::string(strerror(errno)));
+    }
+    if (bind(sockfd, server->ai_addr, server->ai_addrlen) < 0) {
+        throw std::runtime_error("bind failed on port " +
+                                 std::to_string(this->serverPort) + ": " +
+                                 std::string(strerror(errno)));
+    }
+    if (::listen(sockfd, 5) < 0) {
+        throw std::runtime_error("listen failed on port " +
+                                 std::to_string(this->serverPort) + ": " +
+                                 std::string(strerror(errno)));
+    }
 
     FD_ZERO(&activeFdSet);
     FD_SET(sockfd, &activeFdSet);
@@ -172,7 +183,8 @@ void bell::HTTPServer::readFromClient(int clientFd) {
                         std::stoi(line.substr(16, line.size() - 1));
                 }
                 // detect hostname for captive portal
-                if (line.find("Host: connectivitycheck.gstatic.com") != std::string::npos) {
+                if (line.find("Host: connectivitycheck.gstatic.com") !=
+                    std::string::npos) {
                     conn.isCaptivePortal = true;
                     BELL_LOG(info, "http", "Captive portal request detected");
                 }
@@ -182,11 +194,11 @@ void bell::HTTPServer::readFromClient(int clientFd) {
                         goto READBODY;
                     } else {
                         if (!conn.isCaptivePortal) {
-                            findAndHandleRoute(conn.httpMethod, conn.currentLine, clientFd);                        
+                            findAndHandleRoute(conn.httpMethod,
+                                               conn.currentLine, clientFd);
                         } else {
                             this->redirectCaptivePortal(clientFd);
                         }
-
                     }
                 }
             }
@@ -299,7 +311,7 @@ void bell::HTTPServer::redirectCaptivePortal(int connectionFd) {
     this->closeConnection(connectionFd);
 }
 
-void bell::HTTPServer::redirectTo(const std::string & url, int connectionFd) {
+void bell::HTTPServer::redirectTo(const std::string &url, int connectionFd) {
     std::lock_guard lock(this->responseMutex);
     std::stringstream stream;
     stream << "HTTP/1.1 301 Moved Permanently\r\n";

+ 5 - 1
components/spotify/cspot/bell/src/sinks/esp/SPDIFAudioSink.cpp

@@ -85,7 +85,11 @@ bool SPDIFAudioSink::setParams(uint32_t sampleRate, uint8_t channelCount, uint8_
 
     i2s_config_t i2s_config = {
         .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),
-    	.sample_rate = (uint32_t)sample_rate,
+#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)
+    	.sample_rate = (uint32_t) sample_rate,
+#else
+        .sample_rate = (int) sample_rate,
+#endif
         .bits_per_sample = (i2s_bits_per_sample_t)(bitDepth * 2),
         .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
         .communication_format = I2S_COMM_FORMAT_STAND_I2S,

+ 1 - 0
components/spotify/cspot/include/ConfigJSON.h

@@ -18,6 +18,7 @@ public:
 
     uint16_t volume;
     std::string deviceName;
+    std::string apOverride;
     AudioFormat format;
 };
 

+ 3 - 3
components/spotify/cspot/include/ConstantParameters.h

@@ -7,9 +7,9 @@
 extern char deviceId[];
 
 // Hardcoded information sent to spotify servers
-const char * const informationString = "cspot";
-const char * const brandName = "corn";
-const char * const versionString = "cspot-1.0";
+const char * const informationString = "cspot-player";
+const char * const brandName = "cspot";
+const char * const versionString = "cspot-1.1";
 const char * const protocolVersion = "2.7.1";
 const char * const defaultDeviceName = "CSpot";
 const char * const swVersion = "1.0.0";

+ 2 - 2
components/spotify/cspot/include/MercuryManager.h

@@ -69,7 +69,6 @@ private:
   std::unique_ptr<WrappedSemaphore> queueSemaphore;
   unsigned long long lastRequestTimestamp = -1;
   unsigned long long lastPingTimestamp = -1;
-  std::atomic<bool> isRunning = false;
   uint64_t sequenceId;
   uint32_t audioKeySequence;
   audioKeyCallback keyCallback;
@@ -78,10 +77,11 @@ private:
 public:
   MercuryManager(std::unique_ptr<Session> session);
   ~MercuryManager();
+  std::atomic<bool> isRunning = false;
   voidCallback reconnectedCallback;
   uint16_t audioChunkSequence;
   std::shared_ptr<TimeProvider> timeProvider;
-  std::string countryCode;
+  char countryCode[2];
 
   bool timeoutHandler();
   uint64_t execute(MercuryType method, std::string uri, mercuryCallback &callback, mercuryCallback &subscription, mercuryParts &payload);

+ 4 - 4
components/spotify/cspot/include/Player.h

@@ -14,17 +14,17 @@
 #include "SpotifyTrack.h"
 #include "AudioSink.h"
 #include <mutex>
-#include "Queue.h"
 #include "Task.h"
 
 class Player : public bell::Task {
 private:
     std::shared_ptr<MercuryManager> manager;
-    std::shared_ptr<SpotifyTrack> currentTrack = nullptr;
+    SpotifyTrack *currentTrack = nullptr;
+    SpotifyTrack *nextTrack = nullptr;
     std::shared_ptr<AudioSink> audioSink;
     std::mutex loadTrackMutex;
-    // @TODO: Use some actual structure here
-    bell::Queue<std::shared_ptr<SpotifyTrack>> trackQueue;
+    WrappedMutex nextTrackMutex;
+    WrappedMutex currentTrackMutex;
     void runTask();
 
 public:

+ 1 - 0
components/spotify/cspot/include/SpircController.h

@@ -24,6 +24,7 @@ enum class CSpotEventType {
     PREV,
     SEEK,
 	LOAD,
+    PLAYBACK_START
 };
 
 struct CSpotEvent {

+ 4 - 2
components/spotify/cspot/include/SpotifyTrack.h

@@ -15,6 +15,7 @@
 #include "NanoPBHelper.h"
 #include "protobuf/metadata.pb.h"
 #include <cassert>
+#include <atomic>
 
 struct TrackInfo {
     std::string name;
@@ -33,8 +34,8 @@ private:
     void trackInformationCallback(std::unique_ptr<MercuryResponse> response, uint32_t position_ms, bool isPaused);
     void episodeInformationCallback(std::unique_ptr<MercuryResponse> response, uint32_t position_ms, bool isPaused);
     void requestAudioKey(std::vector<uint8_t> fileId, std::vector<uint8_t> trackId, int32_t trackDuration, uint32_t position_ms, bool isPaused);
-    bool countryListContains(std::string countryList, std::string country);
-    bool canPlayTrack();
+    bool countryListContains(char *countryList, char *country);
+    bool canPlayTrack(int altIndex);
     Track trackInfo;
     Episode episodeInfo;
 
@@ -45,6 +46,7 @@ public:
     SpotifyTrack(std::shared_ptr<MercuryManager> manager, std::shared_ptr<TrackReference> trackRef, uint32_t position_ms, bool isPaused);
     ~SpotifyTrack();
     uint64_t reqSeqNum = -1;
+    std::atomic<bool> loaded = false;
     std::function<void()> loadedTrackCallback;
     std::unique_ptr<ChunkedAudioStream> audioStream;
     trackChangedCallback trackInfoReceived;

+ 1 - 3
components/spotify/cspot/include/TrackReference.h

@@ -18,8 +18,6 @@ public:
     TrackReference(TrackRef *ref);
     ~TrackReference();
 
-    TrackRef* ref;
-
     std::vector<uint8_t> gid;
 
     bool isEpisode = false;
@@ -32,4 +30,4 @@ public:
     std::string getMercuryRequestUri();
 };
 
-#endif
+#endif

+ 2 - 2
components/spotify/cspot/protobuf/mercury.options

@@ -1,2 +1,2 @@
-Header.uri max_size:64, fixed_length:false
-Header.method max_size:32, fixed_length:false
+Header.uri max_size:256, fixed_length:false
+Header.method max_size:64, fixed_length:false

+ 9 - 1
components/spotify/cspot/src/ApResolve.cpp

@@ -15,6 +15,8 @@
 #include <fstream>
 #include "Logger.h"
 #include <cJSON.h>
+#include <ConfigJSON.h>
+#include <random>
 
 ApResolve::ApResolve() {}
 
@@ -76,19 +78,25 @@ std::string ApResolve::getApList()
         jsonData += cur;
     }
 
-	close(sockFd);
+    close(sockFd);
 
     return jsonData;
 }
 
 std::string ApResolve::fetchFirstApAddress()
 {
+    if (configMan->apOverride != "")
+    {
+        return configMan->apOverride;
+    }
+
     // Fetch json body
     auto jsonData = getApList();
 
     // Use cJSON to get first ap address
     auto root = cJSON_Parse(jsonData.c_str());
     auto apList = cJSON_GetObjectItemCaseSensitive(root, "ap_list");
+
     auto firstAp = cJSON_GetArrayItem(apList, 0);
     auto data = std::string(firstAp->valuestring);
 

+ 7 - 3
components/spotify/cspot/src/MercuryManager.cpp

@@ -115,6 +115,7 @@ std::shared_ptr<AudioChunk> MercuryManager::fetchAudioChunk(std::vector<uint8_t>
     this->session->shanConn->sendPacket(static_cast<uint8_t>(MercuryType::AUDIO_CHUNK_REQUEST_COMMAND), buffer);
 
     // Used for broken connection detection
+CSPOT_LOG(info, "requesting Chunk %hu", this->audioChunkSequence - 1);				
     this->lastRequestTimestamp = this->timeProvider->getSyncedTimestamp();
     return this->audioChunkManager->registerNewChunk(this->audioChunkSequence - 1, audioKey, startPos, endPos);
 }
@@ -208,16 +209,19 @@ void MercuryManager::updateQueue() {
     if (queueSemaphore->twait() == 0) {
         if (this->queue.size() > 0)
         {
-            auto packet = std::move(this->queue[0]);
+            std::unique_ptr<Packet> packet = std::move(this->queue[0]);
             this->queue.erase(this->queue.begin());
+            if(packet == nullptr){
+                return;
+            }
             CSPOT_LOG(debug, "Received packet with code %d of length %d", packet->command, packet->data.size());
             switch (static_cast<MercuryType>(packet->command))
             {
             case MercuryType::COUNTRY_CODE_RESPONSE:
             {
 
-                countryCode = std::string(packet->data.begin(), packet->data.end());
-                CSPOT_LOG(debug, "Received country code: %s", countryCode.c_str());
+                memcpy(countryCode, packet->data.data(), 2);
+                CSPOT_LOG(debug, "Received country code: %.2s", countryCode);
                 break;
             }
             case MercuryType::AUDIO_KEY_FAILURE_RESPONSE:

+ 69 - 23
components/spotify/cspot/src/Player.cpp

@@ -12,12 +12,24 @@ Player::Player(std::shared_ptr<MercuryManager> manager, std::shared_ptr<AudioSin
 
 void Player::pause()
 {
-    this->currentTrack->audioStream->isPaused = true;
+    if (currentTrack != nullptr)
+    {
+        if (currentTrack->audioStream != nullptr)
+        {
+            this->currentTrack->audioStream->isPaused = true;
+        }
+    }
 }
 
 void Player::play()
 {
-    this->currentTrack->audioStream->isPaused = false;
+    if (currentTrack != nullptr)
+    {
+        if (currentTrack->audioStream != nullptr)
+        {
+            this->currentTrack->audioStream->isPaused = false;
+        }
+    }
 }
 
 void Player::setVolume(uint32_t volume)
@@ -39,7 +51,13 @@ void Player::setVolume(uint32_t volume)
 
 void Player::seekMs(size_t positionMs)
 {
-    this->currentTrack->audioStream->seekMs(positionMs);
+    if (currentTrack != nullptr)
+    {
+        if (currentTrack->audioStream != nullptr)
+        {
+            this->currentTrack->audioStream->seekMs(positionMs);
+        }
+    }
     // VALGRIND_DO_LEAK_CHECK;
 }
 
@@ -50,11 +68,11 @@ void Player::feedPCM(uint8_t *data, size_t len)
     if (this->audioSink->softwareVolumeControl)
     {
         int16_t* psample;
-        uint32_t pmax;
+        int32_t temp;
         psample = (int16_t*)(data);
-        for (int32_t i = 0; i < (len / 2); i++)
+        size_t half_len = len / 2;
+        for (uint32_t i = 0; i < half_len; i++)
         {
-            int32_t temp;
             // Offset data for unsigned sinks
             if (this->audioSink->usign)
             {
@@ -78,27 +96,44 @@ void Player::runTask()
     this->isRunning = true;
     while (isRunning)
     {
-        if (this->trackQueue.wpop(currentTrack)) {
+        if(nextTrack != nullptr && nextTrack->loaded)
+        {
+            this->nextTrackMutex.lock();
+            currentTrack = this->nextTrack;
+            this->nextTrack = nullptr;
+            this->nextTrackMutex.unlock();
+
             currentTrack->audioStream->startPlaybackLoop(pcmOut, 4096 / 4);
             currentTrack->loadedTrackCallback = nullptr;
             currentTrack->audioStream->streamFinishedCallback = nullptr;
             currentTrack->audioStream->audioSink = nullptr;
             currentTrack->audioStream->pcmCallback = nullptr;
-        } else {
-            usleep(100);
+
+            delete currentTrack;
+            currentTrack = nullptr;
         }
+        else
+        {
+            usleep(10000);
+        }
+
     }
     free(pcmOut);
 }
 
 void Player::stop() {
+    CSPOT_LOG(info, "Trying to stop");
     this->isRunning = false;
-    CSPOT_LOG(info, "Stopping player");
-    this->trackQueue.clear();
     cancelCurrentTrack();
-    CSPOT_LOG(info, "Track cancelled");
     std::scoped_lock lock(this->runningMutex);
-    CSPOT_LOG(info, "Done");
+    if(this->nextTrack != nullptr)
+    {
+        delete this->nextTrack;
+    }
+    this->isRunning = false;
+    CSPOT_LOG(info, "Track cancelled");
+    cancelCurrentTrack();
+    CSPOT_LOG(info, "Stopping player");
 }
 
 void Player::cancelCurrentTrack()
@@ -115,21 +150,32 @@ void Player::cancelCurrentTrack()
 void Player::handleLoad(std::shared_ptr<TrackReference> trackReference, std::function<void()>& trackLoadedCallback, uint32_t position_ms, bool isPaused)
 {
     std::lock_guard<std::mutex> guard(loadTrackMutex);
-    cancelCurrentTrack();
 
     pcmDataCallback framesCallback = [=](uint8_t *frames, size_t len) {
         this->feedPCM(frames, len);
      };
 
-    auto loadedLambda = trackLoadedCallback;
+    this->nextTrackMutex.lock();
+    if(this->nextTrack != nullptr)
+    {
+        delete this->nextTrack;
+        this->nextTrack = nullptr;
+    }
+
+    this->nextTrack = new SpotifyTrack(this->manager, trackReference, position_ms, isPaused);
+
+    this->nextTrack->trackInfoReceived = this->trackChanged;
+    this->nextTrack->loadedTrackCallback = [this, framesCallback, trackLoadedCallback]() {
+        trackLoadedCallback();
+
+        this->nextTrackMutex.lock();
+        this->nextTrack->audioStream->streamFinishedCallback = this->endOfFileCallback;
+        this->nextTrack->audioStream->audioSink = this->audioSink;
+        this->nextTrack->audioStream->pcmCallback = framesCallback;
+        this->nextTrack->loaded = true;
+        this->nextTrackMutex.unlock();
 
-    auto track = std::make_shared<SpotifyTrack>(this->manager, trackReference, position_ms, isPaused);
-    track->trackInfoReceived = this->trackChanged;
-    track->loadedTrackCallback = [this, track, framesCallback, loadedLambda]() {
-        loadedLambda();
-        track->audioStream->streamFinishedCallback = this->endOfFileCallback;
-        track->audioStream->audioSink = this->audioSink;
-        track->audioStream->pcmCallback = framesCallback;
-        this->trackQueue.push(track);
+        cancelCurrentTrack();
     };
+    this->nextTrackMutex.unlock();
 }

+ 23 - 11
components/spotify/cspot/src/PlayerState.cpp

@@ -136,37 +136,49 @@ void PlayerState::updatePositionMs(uint32_t position)
     innerFrame.state.position_measured_at = timeProvider->getSyncedTimestamp();
 }
 
+#define FREE(ptr) { free(ptr); ptr = NULL; }
+#define STRDUP(dst, src) if(src != NULL) { dst = strdup(src); } else { FREE(dst); } // strdup null pointer safe
+
 void PlayerState::updateTracks()
 {
     CSPOT_LOG(info, "---- Track count %d", remoteFrame.state.track_count);
+    CSPOT_LOG(info, "---- Inner track count %d", innerFrame.state.track_count);
 
     // free unused tracks
     if(innerFrame.state.track_count > remoteFrame.state.track_count)
     {
         for(uint16_t i = remoteFrame.state.track_count; i < innerFrame.state.track_count; ++i)
         {
-            free(innerFrame.state.track[i].gid);
+            FREE(innerFrame.state.track[i].gid);
+            FREE(innerFrame.state.track[i].uri);
+            FREE(innerFrame.state.track[i].context);
         }
     }
-
+    
     // reallocate memory for new tracks
     innerFrame.state.track = (TrackRef *) realloc(innerFrame.state.track, sizeof(TrackRef) * remoteFrame.state.track_count);
 
     for(uint16_t i = 0; i < remoteFrame.state.track_count; ++i)
     {
-        uint16_t gid_size = remoteFrame.state.track[i].gid->size;
-        // allocate if need more tracks
-        if(i >= innerFrame.state.track_count)
+        if(i >= innerFrame.state.track_count) {
+            innerFrame.state.track[i].gid = NULL;
+            innerFrame.state.track[i].uri = NULL;
+            innerFrame.state.track[i].context = NULL;
+        }
+
+        if(remoteFrame.state.track[i].gid != NULL)
         {
-            innerFrame.state.track[i].gid = (pb_bytes_array_t *) malloc(PB_BYTES_ARRAY_T_ALLOCSIZE(gid_size));
+            uint16_t gid_size = remoteFrame.state.track[i].gid->size;
+            innerFrame.state.track[i].gid = (pb_bytes_array_t *) realloc(innerFrame.state.track[i].gid, PB_BYTES_ARRAY_T_ALLOCSIZE(gid_size));
+            
+            memcpy(innerFrame.state.track[i].gid->bytes, remoteFrame.state.track[i].gid->bytes, gid_size);
+            innerFrame.state.track[i].gid->size = gid_size;
         }
-        memcpy(innerFrame.state.track[i].gid->bytes, remoteFrame.state.track[i].gid->bytes, gid_size);
-        innerFrame.state.track[i].gid->size = gid_size;
         innerFrame.state.track[i].has_queued = remoteFrame.state.track[i].has_queued;
         innerFrame.state.track[i].queued = remoteFrame.state.track[i].queued;
-        // not used?
-        innerFrame.state.track[i].uri = NULL;
-        innerFrame.state.track[i].context = NULL;
+
+        STRDUP(innerFrame.state.track[i].uri, remoteFrame.state.track[i].uri);
+        STRDUP(innerFrame.state.track[i].context, remoteFrame.state.track[i].context);
     }
 
     innerFrame.state.context_uri = (char *) realloc(innerFrame.state.context_uri,

+ 1 - 1
components/spotify/cspot/src/Session.cpp

@@ -168,7 +168,7 @@ std::vector<uint8_t> Session::sendClientHelloRequest()
               clientHello.login_crypto_hello.diffie_hellman.gc);
 
     clientHello.login_crypto_hello.diffie_hellman.server_keys_known = 1;
-    clientHello.build_info.product = Product_PRODUCT_PARTNER;
+    clientHello.build_info.product = Product_PRODUCT_CLIENT;
     clientHello.build_info.platform = Platform2_PLATFORM_LINUX_X86;
     clientHello.build_info.version = SPOTIFY_VERSION;
     clientHello.feature_set.autoupdate2 = true;

+ 3 - 0
components/spotify/cspot/src/SpircController.cpp

@@ -166,6 +166,8 @@ void SpircController::handleFrame(std::vector<uint8_t> &data) {
     }
     case MessageType_kMessageTypeReplace: {
         CSPOT_LOG(debug, "Got replace frame!");
+        state->updateTracks();
+        this->notify();
         break;
     }
     case MessageType_kMessageTypeShuffle: {
@@ -191,6 +193,7 @@ void SpircController::loadTrack(uint32_t position_ms, bool isPaused) {
     std::function<void()> loadedLambda = [=]() {
         // Loading finished, notify that playback started
         setPause(isPaused, false);
+        sendEvent(CSpotEventType::PLAYBACK_START);
     };
 
     player->handleLoad(state->getCurrentTrack(), loadedLambda, position_ms,

+ 54 - 24
components/spotify/cspot/src/SpotifyTrack.cpp

@@ -39,11 +39,12 @@ SpotifyTrack::~SpotifyTrack()
     pb_release(Episode_fields, &this->episodeInfo);
 }
 
-bool SpotifyTrack::countryListContains(std::string countryList, std::string country)
+bool SpotifyTrack::countryListContains(char *countryList, char *country)
 {
-    for (int x = 0; x < countryList.size(); x += 2)
+    uint16_t countryList_length = strlen(countryList);
+    for (int x = 0; x < countryList_length; x += 2)
     {
-        if (countryList.substr(x, 2) == country)
+        if (countryList[x] == country[0] && countryList[x + 1] == country[1])
         {
             return true;
         }
@@ -51,21 +52,38 @@ bool SpotifyTrack::countryListContains(std::string countryList, std::string coun
     return false;
 }
 
-bool SpotifyTrack::canPlayTrack()
+bool SpotifyTrack::canPlayTrack(int altIndex)
 {
-    for (int x = 0; x < trackInfo.restriction_count; x++)
+    if(altIndex < 0)
     {
-        if (trackInfo.restriction[x].countries_allowed != nullptr)
+        for (int x = 0; x < trackInfo.restriction_count; x++)
         {
-            return countryListContains(std::string(trackInfo.restriction[x].countries_allowed), manager->countryCode);
-        }
+            if (trackInfo.restriction[x].countries_allowed != nullptr)
+            {
+                return countryListContains(trackInfo.restriction[x].countries_allowed, manager->countryCode);
+            }
 
-        if (trackInfo.restriction[x].countries_forbidden != nullptr)
-        {
-            return !countryListContains(std::string(trackInfo.restriction[x].countries_forbidden), manager->countryCode);
+            if (trackInfo.restriction[x].countries_forbidden != nullptr)
+            {
+                return !countryListContains(trackInfo.restriction[x].countries_forbidden, manager->countryCode);
+            }
         }
     }
+    else
+    {
+        for (int x = 0; x < trackInfo.alternative[altIndex].restriction_count; x++)
+        {
+            if (trackInfo.alternative[altIndex].restriction[x].countries_allowed != nullptr)
+            {
+                return countryListContains(trackInfo.alternative[altIndex].restriction[x].countries_allowed, manager->countryCode);
+            }
 
+            if (trackInfo.alternative[altIndex].restriction[x].countries_forbidden != nullptr)
+            {
+                return !countryListContains(trackInfo.alternative[altIndex].restriction[x].countries_forbidden, manager->countryCode);
+            }
+        }
+    }
     return true;
 }
 
@@ -81,16 +99,12 @@ void SpotifyTrack::trackInformationCallback(std::unique_ptr<MercuryResponse> res
     CSPOT_LOG(info, "Track name: %s", trackInfo.name);
     CSPOT_LOG(info, "Track duration: %d", trackInfo.duration);
     CSPOT_LOG(debug, "trackInfo.restriction.size() = %d", trackInfo.restriction_count);
-    int altIndex = 0;
-    while (!canPlayTrack())
+
+    int altIndex = -1;
+    while (!canPlayTrack(altIndex))
     {
-        std::swap(trackInfo.restriction, trackInfo.alternative[altIndex].restriction);
-        std::swap(trackInfo.restriction_count, trackInfo.alternative[altIndex].restriction_count);
-        std::swap(trackInfo.file, trackInfo.alternative[altIndex].file);
-        std::swap(trackInfo.file_count, trackInfo.alternative[altIndex].file_count);
-        std::swap(trackInfo.gid, trackInfo.alternative[altIndex].gid);
-        CSPOT_LOG(info, "Trying alternative %d", altIndex);
         altIndex++;
+        CSPOT_LOG(info, "Trying alternative %d", altIndex);
     
         if(altIndex > trackInfo.alternative_count) {
             // no alternatives for song
@@ -98,15 +112,31 @@ void SpotifyTrack::trackInformationCallback(std::unique_ptr<MercuryResponse> res
         }
     }
 
-    auto trackId = pbArrayToVector(trackInfo.gid);
+    std::vector<uint8_t> trackId;
     this->fileId = std::vector<uint8_t>();
 
-    for (int x = 0; x < trackInfo.file_count; x++)
+    if(altIndex < 0)
     {
-        if (trackInfo.file[x].format == configMan->format)
+        trackId = pbArrayToVector(trackInfo.gid);
+        for (int x = 0; x < trackInfo.file_count; x++)
         {
-            this->fileId = pbArrayToVector(trackInfo.file[x].file_id);
-            break; // If file found stop searching
+            if (trackInfo.file[x].format == configMan->format)
+            {
+                this->fileId = pbArrayToVector(trackInfo.file[x].file_id);
+                break; // If file found stop searching
+            }
+        }
+    }
+    else
+    {
+        trackId = pbArrayToVector(trackInfo.alternative[altIndex].gid);
+        for (int x = 0; x < trackInfo.alternative[altIndex].file_count; x++)
+        {
+            if (trackInfo.alternative[altIndex].file[x].format == configMan->format)
+            {
+                this->fileId = pbArrayToVector(trackInfo.alternative[altIndex].file[x].file_id);
+                break; // If file found stop searching
+            }
         }
     }
 

+ 1 - 1
components/spotify/cspot/src/ZeroconfAuthenticator.cpp

@@ -8,7 +8,7 @@
 #include "ConfigJSON.h"
 
 // provide weak deviceId (see ConstantParameters.h)
-char deviceId[] __attribute__((weak)) = "162137fd329622137a14901634264e6f332e2422";
+char deviceId[] __attribute__((weak)) = "142137fd329622137a14901634264e6f332e2411";
 
 ZeroconfAuthenticator::ZeroconfAuthenticator(authCallback callback, std::shared_ptr<bell::BaseHTTPServer> httpServer) {
     this->gotBlobCallback = callback;

+ 2 - 0
components/squeezelite/decode_external.c

@@ -364,6 +364,8 @@ static bool cspot_cmd_handler(cspot_event_t cmd, va_list args)
 		LOG_INFO("CSpot disconnected");
 		break;
 	case CSPOT_TRACK:
+		_buf_flush(outputbuf);		
+		abort_sink = true;
 		LOG_INFO("CSpot sink new track rate %d", output.next_sample_rate);
 		break;
 	case CSPOT_PLAY: