2
0
Эх сурвалжийг харах

make it fit in allocated space

philippe44 2 жил өмнө
parent
commit
fc78b36c1f
24 өөрчлөгдсөн 421 нэмэгдсэн , 230 устгасан
  1. 6 2
      components/spotify/CMakeLists.txt
  2. 95 139
      components/spotify/Shim.cpp
  3. 36 15
      components/spotify/cspot/bell/CMakeLists.txt
  4. 38 1
      components/spotify/cspot/bell/main/io/URLParser.cpp
  5. 10 0
      components/spotify/cspot/bell/main/io/include/HTTPClient.h
  6. 10 3
      components/spotify/cspot/bell/main/io/include/URLParser.h
  7. 5 3
      components/spotify/cspot/bell/main/platform/MDNSService.h
  8. 11 6
      components/spotify/cspot/bell/main/platform/apple/MDNSService.cpp
  9. 13 2
      components/spotify/cspot/bell/main/platform/esp/MDNSService.cpp
  10. 66 38
      components/spotify/cspot/bell/main/platform/linux/MDNSService.cpp
  11. 20 10
      components/spotify/cspot/bell/main/platform/win32/MDNSService.cpp
  12. 0 1
      components/spotify/cspot/bell/main/utilities/include/BellUtils.h
  13. 1 1
      components/spotify/cspot/bell/main/utilities/include/Crypto.h
  14. 4 0
      components/spotify/cspot/include/ApResolve.h
  15. 0 1
      components/spotify/cspot/include/CDNTrackStream.h
  16. 2 0
      components/spotify/cspot/include/LoginBlob.h
  17. 11 0
      components/spotify/cspot/src/AccessKeyFetcher.cpp
  18. 7 0
      components/spotify/cspot/src/ApResolve.cpp
  19. 8 2
      components/spotify/cspot/src/CDNTrackStream.cpp
  20. 58 1
      components/spotify/cspot/src/LoginBlob.cpp
  21. 1 1
      components/spotify/cspot/src/TrackPlayer.cpp
  22. 1 1
      components/spotify/cspot_sink.c
  23. 5 2
      components/spotify/cspot_sink.h
  24. 13 1
      components/squeezelite/decode_external.c

+ 6 - 2
components/spotify/CMakeLists.txt

@@ -1,5 +1,5 @@
 # this must be set *before* idf_component_register
-set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD 20)
 
 idf_component_register(
 		SRC_DIRS . 
@@ -14,8 +14,12 @@ add_definitions(-Wno-unused-variable -Wno-unused-const-variable -Wchar-subscript
 
 set(BELL_DISABLE_CODECS ON)
 set(BELL_DISABLE_SINKS ON)
+set(BELL_DISABLE_FMT ON)
+set(BELL_DISABLE_REGEX ON)
+set(BELL_ONLY_CJSON ON)
 set(CSPOT_TARGET_ESP32 ON)
-# becase CMake is so broken, the cache set below overrides a normal "set" for the first build
+
+# because CMake is so broken, the cache set below overrides a normal "set" for the first build
 set(BELL_EXTERNAL_VORBIS "idf::codecs" CACHE STRING "provide own codecs")
 set(BELL_EXTERNAL_CJSON "idf::json" CACHE STRING "provide own CJSON")
 

+ 95 - 139
components/spotify/Shim.cpp

@@ -29,40 +29,7 @@
 #include "platform_config.h"
 #include "tools.h"
 
-//#include "time.h"
-
-/*
-#include <stdio.h>
-#include <string.h>
-#include <inttypes.h>
-#include "sdkconfig.h"
-#include "freertos/FreeRTOS.h"
-#include "freertos/task.h"
-#include "esp_system.h"
-#include "esp_wifi.h"
-#include "esp_event.h"
-#include "esp_log.h"
-#include "esp_http_server.h"
-
-#include <ConstantParameters.h>
-#include <Session.h>
-#include <SpircController.h>
-#include <MercuryManager.h>
-#include <ZeroconfAuthenticator.h>
-#include <ApResolve.h>
-#include <HTTPServer.h>
-#include "ConfigJSON.h"
-#include "Logger.h"
-
-#include "platform_config.h"
-#include "tools.h"
-#include "cspot_private.h"
-#include "cspot_sink.h"
-*/
-
-static const char *TAG = "cspot";
-
-class cspotPlayer *player;
+static class cspotPlayer *player;
 
 /****************************************************************************************
  * Chunk manager class (task)
@@ -72,27 +39,33 @@ class chunkManager : public bell::Task {
 public:
     std::atomic<bool> isRunning = true;
     std::atomic<bool> isPaused = true;
-    chunkManager(std::shared_ptr<bell::CentralAudioBuffer> centralAudioBuffer, std::function<void()> trackHandler, std::function<void(const uint8_t*, size_t)> audioHandler);
+    chunkManager(std::shared_ptr<bell::CentralAudioBuffer> centralAudioBuffer, std::function<void()> trackHandler,
+                 std::function<void(const uint8_t*, size_t)> dataHandler);
     void teardown();
 
 private:
     std::shared_ptr<bell::CentralAudioBuffer> centralAudioBuffer;
     std::function<void()> trackHandler;
-    std::function<void(const uint8_t*, size_t)> audioHandler;
+    std::function<void(const uint8_t*, size_t)> dataHandler;
     std::mutex runningMutex;
 
     void runTask() override;
 };
 
 chunkManager::chunkManager(std::shared_ptr<bell::CentralAudioBuffer> centralAudioBuffer,
-                            std::function<void()> trackHandler, std::function<void(const uint8_t*, size_t)> audioHandler)
-    : bell::Task("player", 4 * 1024, 0, 0) {
+                            std::function<void()> trackHandler, std::function<void(const uint8_t*, size_t)> dataHandler)
+    : bell::Task("chunker", 4 * 1024, 0, 0) {
     this->centralAudioBuffer = centralAudioBuffer;
     this->trackHandler = trackHandler;
-    this->audioHandler = audioHandler;
+    this->dataHandler = dataHandler;
     startTask();
 }
 
+void chunkManager::teardown() {
+    isRunning = false;
+    std::scoped_lock lock(runningMutex);
+}
+
 void chunkManager::runTask() {
     std::scoped_lock lock(runningMutex);
     size_t lastHash = 0;
@@ -118,15 +91,10 @@ void chunkManager::runTask() {
             trackHandler();
         }
 
-        audioHandler(chunk->pcmData, chunk->pcmSize);
+        dataHandler(chunk->pcmData, chunk->pcmSize);
     }
 }
 
-void chunkManager::teardown() {
-    isRunning = false;
-    std::scoped_lock lock(runningMutex);
-}
-
 /****************************************************************************************
  * Player's main class  & task
  */
@@ -134,17 +102,14 @@ void chunkManager::teardown() {
 class cspotPlayer : public bell::Task {
 private:
     std::string name;
-    bool playback = false;
     bell::WrappedSemaphore clientConnected;
     std::shared_ptr<bell::CentralAudioBuffer> centralAudioBuffer;
 
-    TimerHandle_t trackTimer;
-
     int startOffset, volume = 0, bitrate = 160;
     httpd_handle_t serverHandle;
     int serverPort;
     cspot_cmd_cb_t cmdHandler;
-	cspot_data_cb_t dataHandler;
+    cspot_data_cb_t dataHandler;
 
     std::shared_ptr<cspot::LoginBlob> blob;
     std::unique_ptr<cspot::SpircHandler> spirc;
@@ -156,30 +121,28 @@ private:
     void runTask();
 
 public:
-    std::atomic<bool> trackNotify = false;
+    typedef enum {TRACK_INIT, TRACK_NOTIFY, TRACK_STREAM, TRACK_END} TrackStatus;
+    std::atomic<TrackStatus> trackStatus = TRACK_INIT;
 
     cspotPlayer(const char*, httpd_handle_t, int, cspot_cmd_cb_t, cspot_data_cb_t);
-    ~cspotPlayer();
     esp_err_t handleGET(httpd_req_t *request);
     esp_err_t handlePOST(httpd_req_t *request);
+    void command(cspot_event_t event);
 };
 
 cspotPlayer::cspotPlayer(const char* name, httpd_handle_t server, int port, cspot_cmd_cb_t cmdHandler, cspot_data_cb_t dataHandler) :
                         bell::Task("playerInstance", 32 * 1024, 0, 0),
                         serverHandle(server), serverPort(port),
                         cmdHandler(cmdHandler), dataHandler(dataHandler) {
-                            
+
     cJSON *item, *config = config_alloc_get_cjson("cspot_config");
     if ((item = cJSON_GetObjectItem(config, "volume")) != NULL) volume = item->valueint;
     if ((item = cJSON_GetObjectItem(config, "bitrate")) != NULL) bitrate = item->valueint;
     if ((item = cJSON_GetObjectItem(config, "deviceName") ) != NULL) this->name = item->valuestring;
     else this->name = name;
     cJSON_Delete(config);
-    
-    if (bitrate != 96 && bitrate != 160 && bitrate != 320) bitrate = 160;
-}
 
-cspotPlayer::~cspotPlayer() {
+    if (bitrate != 96 && bitrate != 160 && bitrate != 320) bitrate = 160;
 }
 
 extern "C" {
@@ -190,10 +153,6 @@ extern "C" {
     static esp_err_t handlePOST(httpd_req_t *request) {
         return player->handlePOST(request);
     }
-
-    static void trackTimerHandler(TimerHandle_t xTimer) {
-        player->trackNotify = true;
-    }
 }
 
 esp_err_t cspotPlayer::handleGET(httpd_req_t *request) {
@@ -217,7 +176,7 @@ esp_err_t cspotPlayer::handlePOST(httpd_req_t *request) {
    cJSON_AddNumberToObject(response, "status", 101);
    cJSON_AddStringToObject(response, "statusString", "ERROR-OK");
    cJSON_AddNumberToObject(response, "spotifyError", 0);
-   
+
     // get body if any (add '\0' at the end if used as string)
 	if (request->content_len) {
 		char* body = (char*) calloc(1, request->content_len + 1);
@@ -248,7 +207,7 @@ esp_err_t cspotPlayer::handlePOST(httpd_req_t *request) {
 
     esp_err_t rc = httpd_resp_send(request, responseStr, strlen(responseStr));
     free(responseStr);
-    
+
     return rc;
 }
 
@@ -258,18 +217,14 @@ void cspotPlayer::eventHandler(std::unique_ptr<cspot::SpircHandler::Event> event
         centralAudioBuffer->clearBuffer();
 
         // we are not playing anymore
-        xTimerStop(trackTimer, portMAX_DELAY);
-        trackNotify = false;
-        playback = false;
-
+        trackStatus = TRACK_INIT;
         // memorize position for when track's beginning will be detected
         startOffset = std::get<int>(event->data);
-
-        cmdHandler(CSPOT_START, 44100);
-        CSPOT_LOG(info, "start track <%s>", spirc->getTrackPlayer()->getCurrentTrackInfo().name.c_str());
-
         // Spotify servers do not send volume at connection
         spirc->setRemoteVolume(volume);
+
+        cmdHandler(CSPOT_START, 44100);
+        CSPOT_LOG(info, "restart");
         break;
     }
     case cspot::SpircHandler::EventType::PLAY_PAUSE: {
@@ -280,7 +235,7 @@ void cspotPlayer::eventHandler(std::unique_ptr<cspot::SpircHandler::Event> event
     }
     case cspot::SpircHandler::EventType::TRACK_INFO: {
         auto trackInfo = std::get<cspot::CDNTrackStream::TrackInfo>(event->data);
-        cmdHandler(CSPOT_TRACK, trackInfo.duration, startOffset, trackInfo.artist.c_str(),
+        cmdHandler(CSPOT_TRACK_INFO, trackInfo.duration, startOffset, trackInfo.artist.c_str(),
                        trackInfo.album.c_str(), trackInfo.name.c_str(), trackInfo.imageUrl.c_str());
         spirc->updatePositionMs(startOffset);
         startOffset = 0;
@@ -296,7 +251,6 @@ void cspotPlayer::eventHandler(std::unique_ptr<cspot::SpircHandler::Event> event
     }
     case cspot::SpircHandler::EventType::DISC:
         centralAudioBuffer->clearBuffer();
-        xTimerStop(trackTimer, portMAX_DELAY);
         cmdHandler(CSPOT_DISC);
         chunker->teardown();
         break;
@@ -306,6 +260,7 @@ void cspotPlayer::eventHandler(std::unique_ptr<cspot::SpircHandler::Event> event
         break;
     }
     case cspot::SpircHandler::EventType::DEPLETED:
+        trackStatus = TRACK_END;
         CSPOT_LOG(info, "playlist ended, no track left to play");
         break;
     case cspot::SpircHandler::EventType::VOLUME:
@@ -318,18 +273,38 @@ void cspotPlayer::eventHandler(std::unique_ptr<cspot::SpircHandler::Event> event
 }
 
 void cspotPlayer::trackHandler(void) {
-    if (playback) {
-        uint32_t remains;
-        auto trackInfo = spirc->getTrackPlayer()->getCurrentTrackInfo();
-        // if this is not first track, estimate when the current one will finish
-        cmdHandler(CSPOT_REMAINING, &remains);
-        if (remains > 100) xTimerChangePeriod(trackTimer, pdMS_TO_TICKS(remains), portMAX_DELAY);
-        else trackNotify = true;
-        CSPOT_LOG(info, "next track <%s> in cspot buffers, remaining %d ms", trackInfo.name.c_str(), remains);
-    } else {
-        trackNotify = true;
-        playback = true;
-    }  
+    // this is just informative
+    auto trackInfo = spirc->getTrackPlayer()->getCurrentTrackInfo();
+    uint32_t remains;
+    cmdHandler(CSPOT_QUERY_REMAINING, &remains);
+    CSPOT_LOG(info, "next track <%s> will play in %d ms", trackInfo.name.c_str(), remains);
+
+    // inform sink of track beginning
+    trackStatus = TRACK_NOTIFY;
+    cmdHandler(CSPOT_TRACK_MARK);
+}
+
+void cspotPlayer::command(cspot_event_t event) {
+    if (!spirc) return;
+
+    // switch...case consume a ton of extra .rodata
+    if (event == CSPOT_PREV) spirc->previousSong();
+    else if (event == CSPOT_NEXT) spirc->nextSong();
+    else if (event == CSPOT_TOGGLE)	spirc->setPause(!chunker->isPaused);
+    else if (event == CSPOT_STOP || event == CSPOT_PAUSE) spirc->setPause(true);
+    else if (event == CSPOT_PLAY) spirc->setPause(false);
+    else if (event == CSPOT_DISC) spirc->disconnect();
+    else if (event == CSPOT_VOLUME_UP) {
+        volume += (UINT16_MAX / 50);
+        volume = std::min(volume, UINT16_MAX);
+        cmdHandler(CSPOT_VOLUME, volume);
+        spirc->setRemoteVolume(volume);
+    } else if (event == CSPOT_VOLUME_DOWN) {
+        volume -= (UINT16_MAX / 50);
+        volume = std::max(volume, 0);
+        cmdHandler(CSPOT_VOLUME, volume);
+        spirc->setRemoteVolume(volume);
+	}
 }
 
 void cspotPlayer::runTask() {
@@ -352,27 +327,26 @@ void cspotPlayer::runTask() {
     // Register mdns service, for spotify to find us
     bell::MDNSService::registerService( blob->getDeviceName(), "_spotify-connect", "_tcp", "", serverPort,
             { {"VERSION", "1.0"}, {"CPath", "/spotify_info"}, {"Stack", "SP"} });
-                            
+
                                 static int count = 0;
     // gone with the wind...
     while (1) {
         clientConnected.wait();
 
         CSPOT_LOG(info, "Spotify client connected for %s", name.c_str());
-        
-        centralAudioBuffer = std::make_shared<bell::CentralAudioBuffer>(32);        
+
+        centralAudioBuffer = std::make_shared<bell::CentralAudioBuffer>(32);
         auto ctx = cspot::Context::createFromBlob(blob);
-             
+
         if (bitrate == 320) ctx->config.audioFormat = AudioFormat_OGG_VORBIS_320;
         else if (bitrate == 96) ctx->config.audioFormat = AudioFormat_OGG_VORBIS_96;
-        else ctx->config.audioFormat = AudioFormat_OGG_VORBIS_160;            
+        else ctx->config.audioFormat = AudioFormat_OGG_VORBIS_160;
 
         ctx->session->connectWithRandomAp();
-        auto token = ctx->session->authenticate(blob);      
+        auto token = ctx->session->authenticate(blob);
 
         // Auth successful
         if (token.size() > 0) {
-            trackTimer = xTimerCreate("trackTimer", pdMS_TO_TICKS(1000), pdFALSE, NULL, trackTimerHandler);
             spirc = std::make_unique<cspot::SpircHandler>(ctx);
 
             // set call back to calculate a hash on trackId
@@ -398,32 +372,50 @@ void cspotPlayer::runTask() {
                 [this](const uint8_t* data, size_t bytes) {
                     return dataHandler(data, bytes);
              });
+             
+             // set volume at connection
+             cmdHandler(CSPOT_VOLUME, volume);
 
             // exit when player has stopped (received a DISC)
             while (chunker->isRunning) {
                 ctx->session->handlePacket();
 
-                // inform Spotify that next track has started (don't need to be super accurate)
-                if (trackNotify) {
-                    CSPOT_LOG(info, "next track's audio has reached DAC");
-                    spirc->notifyAudioReachedPlayback();
-                    trackNotify = false;
+                // low-accuracy polling events
+                if (trackStatus == TRACK_NOTIFY) {
+                    // inform Spotify that next track has started (don't need to be super accurate)
+                    uint32_t started;
+                    cmdHandler(CSPOT_QUERY_STARTED, &started);
+                    if (started) {
+                        CSPOT_LOG(info, "next track's audio has reached DAC");
+                        spirc->notifyAudioReachedPlayback();
+                        trackStatus = TRACK_STREAM;
+                    }
+                } else if (trackStatus == TRACK_END) {
+                    // wait for end of last track
+                    uint32_t remains;
+                    cmdHandler(CSPOT_QUERY_REMAINING, &remains);
+                    if (!remains) {
+                        CSPOT_LOG(info, "last track finished");
+                        trackStatus = TRACK_INIT;
+                        cmdHandler(CSPOT_STOP);
+                        spirc->setPause(true);
+                    }
                 }
             }
 
-            xTimerDelete(trackTimer, portMAX_DELAY);
             spirc->disconnect();
-                       
+            spirc.reset();
+
             CSPOT_LOG(info, "disconnecting player %s", name.c_str());
         }
-        
+
         // we want to release memory ASAP and fore sure
         centralAudioBuffer.reset();
         ctx.reset();
         token.clear();
 
         // update volume when we disconnect
-        cJSON *item, *config = config_alloc_get_cjson("cspot_config");
+        cJSON *config = config_alloc_get_cjson("cspot_config");
         cJSON_DeleteItemFromObject(config, "volume");
         cJSON_AddNumberToObject(config, "volume", volume);
         config_set_cjson_str_and_free("cspot_config", config);
@@ -433,7 +425,7 @@ void cspotPlayer::runTask() {
 /****************************************************************************************
  * API to create and start a cspot instance
  */
- 
+
 struct cspot_s* cspot_create(const char *name, httpd_handle_t server, int port, cspot_cmd_cb_t cmd_cb, cspot_data_cb_t data_cb) {
 	bell::setDefaultLogger();
     player = new cspotPlayer(name, server, port, cmd_cb, data_cb);
@@ -444,44 +436,8 @@ struct cspot_s* cspot_create(const char *name, httpd_handle_t server, int port,
 /****************************************************************************************
  * Commands sent by local buttons/actions
  */
- 
-bool cspot_cmd(struct cspot_s* ctx, cspot_event_t event, void *param) {
-	// we might have no controller left
-/*
-	if (!spircController.use_count()) return false;
-
-	switch(event) {
-		case CSPOT_PREV:
-			spircController->prevSong();
-			break;
-		case CSPOT_NEXT:
-			spircController->nextSong();
-			break;
-		case CSPOT_TOGGLE:
-			spircController->playToggle();
-			break;
-		case CSPOT_PAUSE:
-			spircController->setPause(true);
-			break;
-		case CSPOT_PLAY:
-			spircController->setPause(false);
-			break;
-		case CSPOT_DISC:
-			spircController->disconnect();
-			break;
-		case CSPOT_STOP:
-			spircController->stopPlayer();
-			break;
-		case CSPOT_VOLUME_UP:
-			spircController->adjustVolume(MAX_VOLUME / 100 + 1);
-			break;
-		case CSPOT_VOLUME_DOWN:
-			spircController->adjustVolume(-(MAX_VOLUME / 100 + 1));
-			break;
-		default:
-			break;
-	}
-*/
 
+bool cspot_cmd(struct cspot_s* ctx, cspot_event_t event, void *param) {
+    player->command(event);
 	return true;
 }

+ 36 - 15
components/spotify/cspot/bell/CMakeLists.txt

@@ -17,9 +17,17 @@ option(BELL_SINK_ALSA "Enable ALSA audio sink" OFF)
 option(BELL_SINK_PORTAUDIO "Enable PortAudio sink" OFF)
 
 # cJSON wrapper
-option(BELL_DISABLE_CJSON "Disable cJSON and JSONObject completely" OFF)
+option(BELL_ONLY_CJSON "Use only cJSON, not Nlohmann")
 set(BELL_EXTERNAL_CJSON "" CACHE STRING "External cJSON library target name, optional")
 
+# vorbis
+set(BELL_EXTERNAL_VORBIS "" CACHE STRING "External Vorbis library target name, optional")
+option(BELL_VORBIS_FLOAT "Use floating point Vorbis API" OFF)
+
+# fmt & regex
+option(BELL_DISABLE_FMT "Don't use std::fmt (saves space)" OFF)
+option(BELL_DISABLE_REGEX "Don't use std::regex (saves space)" OFF)
+
 # disable json tests
 set(JSON_BuildTests OFF CACHE INTERNAL "")
 
@@ -46,13 +54,16 @@ if(NOT BELL_DISABLE_CODECS)
 endif()
 
 message(STATUS "    Disable built-in audio sinks: ${BELL_DISABLE_SINKS}")
+message(STATUS "    Use Vorbis float version: ${BELL_VORBIS_FLOAT}")
 
 if(NOT BELL_DISABLE_SINKS)
     message(STATUS "    - ALSA sink: ${BELL_SINK_ALSA}")
     message(STATUS "    - PortAudio sink: ${BELL_SINK_PORTAUDIO}")
 endif()
 
-message(STATUS "    Disable cJSON and JSONObject: ${BELL_DISABLE_CJSON}")
+message(STATUS "    Use cJSON only: ${BELL_ONLY_CJSON}")
+message(STATUS "    Disable Fmt: ${BELL_DISABLE_FMT}")
+message(STATUS "    Disable Regex: ${BELL_DISABLE_REGEX}")
 
 # Include nanoPB library
 set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/external/nanopb/extra")
@@ -212,7 +223,7 @@ else()
     list(REMOVE_ITEM SOURCES "${IO_DIR}/EncodedAudioStream.cpp")
 endif() 
 
-if(BELL_EXTERNAL_VORBIS)
+if(NOT BELL_EXTERNAL_VORBIS STREQUAL "")
     message(STATUS "Using external Vorbis codec ${BELL_EXTERNAL_VORBIS}")
     list(APPEND EXTRA_LIBS ${BELL_EXTERNAL_VORBIS})
 else()  
@@ -254,21 +265,19 @@ if(NOT BELL_DISABLE_SINKS)
     list(APPEND SOURCES ${SINK_SOURCES})
 endif()
 
-if(BELL_DISABLE_CJSON)
-    list(REMOVE_ITEM SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/main/io/JSONObject.cpp")
-else()
+if(NOT BELL_ONLY_CJSON)
     add_subdirectory(external/nlohmann_json)
     list(APPEND EXTRA_LIBS nlohmann_json::nlohmann_json)
-    if(BELL_EXTERNAL_CJSON)
-        list(APPEND EXTRA_LIBS ${BELL_EXTERNAL_CJSON})
-    else()
-        list(APPEND SOURCES "external/cJSON/cJSON.c")
-        list(APPEND EXTRA_INCLUDES "external/cJSON")
-    endif()
-endif()
+endif()	
 
-if (BELL_DISABLE_FMT)
-else()
+if(BELL_EXTERNAL_CJSON)
+	list(APPEND EXTRA_LIBS ${BELL_EXTERNAL_CJSON})
+else()	
+	list(APPEND SOURCES "external/cJSON/cJSON.c")
+    list(APPEND EXTRA_INCLUDES "external/cJSON")
+endif()	
+
+if (NOT BELL_DISABLE_FMT)
     list(APPEND EXTRA_INCLUDES "external/fmt/include")
 endif()
 
@@ -306,6 +315,18 @@ if(BELL_VORBIS_FLOAT)
     target_compile_definitions(bell PUBLIC BELL_VORBIS_FLOAT)
 endif() 
 
+if(BELL_DISABLE_FMT)
+	target_compile_definitions(bell PUBLIC BELL_DISABLE_FMT)
+endif()
+
+if(BELL_DISABLE_REGEX)
+    target_compile_definitions(bell PUBLIC BELL_DISABLE_REGEX)
+endif()	
+
+if(BELL_ONLY_CJSON)
+    target_compile_definitions(bell PUBLIC BELL_ONLY_CJSON)
+endif()	
+
 if(WIN32 OR CMAKE_SYSTEM_NAME STREQUAL "SunOS")
     target_compile_definitions(bell PUBLIC PB_NO_STATIC_ASSERT)
 endif()

+ 38 - 1
components/spotify/cspot/bell/main/io/URLParser.cpp

@@ -1 +1,38 @@
-#include "URLParser.h"
+#include "URLParser.h"
+
+namespace bell {
+
+#ifdef BELL_DISABLE_REGEX
+void URLParser::parse(const char* url, std::vector<std::string>& match) {
+    match[0] = url;
+    char scratch[512];
+    
+    // get schema [http(s)]://
+    if (sscanf(url, "%[^:]:/", scratch) > 0) match[1] = scratch;
+    
+    // get host  http(s)://[host]
+    if (sscanf(url, "htt%*[^:]://%512[^/#]", scratch) > 0) match[2] = scratch;
+
+    // get the path
+    url = strstr(url, match[2].c_str());
+    if (!url || *url == '\0') return;
+    url += match[2].size();
+    if (sscanf(url, "/%512[^?]", scratch) > 0) match[3] = scratch;
+    
+    // get the query
+    if (match[3].size()) url += match[3].size() + 1;
+    if (sscanf(url, "?%512[^#]", scratch) > 0) match[4] = scratch;
+
+    // get the hash
+    if (match[4].size()) url += match[4].size() + 1;
+    if (sscanf(url, "#%512s", scratch) > 0) match[5] = scratch;
+
+    // fix the acquired items
+    match[3] = "/" + match[3];
+    if (match[4].size()) match[4] = "?" + match[4];
+}    
+#else    
+const std::regex URLParser::urlParseRegex = std::regex(
+      "^(?:([^:/?#]+):)?(?://([^/?#]*))?([^?#]*)(\\?(?:[^#]*))?(#(?:.*))?");
+#endif
+} 

+ 10 - 0
components/spotify/cspot/bell/main/io/include/HTTPClient.h

@@ -15,7 +15,9 @@
 #include "ByteStream.h"
 #include "SocketStream.h"
 #include "URLParser.h"
+#ifndef BELL_DISABLE_FMT
 #include "fmt/core.h"
+#endif
 #include "picohttpparser.h"
 
 namespace bell {
@@ -29,11 +31,19 @@ class HTTPClient {
   // Helper over ValueHeader, formatting a HTTP bytes range
   struct RangeHeader {
     static ValueHeader range(int32_t from, int32_t to) {
+#ifndef BELL_DISABLE_FMT        
       return ValueHeader{"Range", fmt::format("bytes={}-{}", from, to)};
+#else      
+      return ValueHeader{"Range", "bytes=" + std::to_string(from) + "-" + std::to_string(to)  };
+#endif  
     }
 
     static ValueHeader last(int32_t nbytes) {
+#ifndef BELL_DISABLE_FMT        
       return ValueHeader{"Range", fmt::format("bytes=-{}", nbytes)};
+#else      
+      return ValueHeader{"Range", "bytes=-" + std::to_string(nbytes)};
+#endif  
     }
   };
 

+ 10 - 3
components/spotify/cspot/bell/main/io/include/URLParser.h

@@ -58,16 +58,23 @@ class URLParser {
 
   std::string schema = "http";
   std::string path;
-  std::regex urlParseRegex = std::regex(
-      "^(?:([^:/?#]+):)?(?://([^/?#]*))?([^?#]*)(\\?(?:[^#]*))?(#(?:.*))?");
+#ifdef BELL_DISABLE_REGEX
+  void parse(const char* url, std::vector<std::string>& match);
+#else  
+  static const std::regex urlParseRegex;
+#endif
 
   static URLParser parse(const std::string& url) {
     URLParser parser;
 
     // apply parser.urlParseRegex to url
+#ifdef BELL_DISABLE_REGEX
+    std::vector<std::string> match(6);
+    parser.parse(url.c_str(), match);
+#else    
     std::cmatch match;
-
     std::regex_match(url.c_str(), match, parser.urlParseRegex);
+#endif    
 
     if (match.size() < 3) {
       throw std::invalid_argument("Invalid URL");

+ 5 - 3
components/spotify/cspot/bell/main/platform/MDNSService.h

@@ -2,12 +2,14 @@
 
 #include <map>
 #include <string>
+#include <memory>
 
 namespace bell {
 
 class MDNSService {
-  public:
-	static void* registerService(
+public:
+	virtual ~MDNSService() { }
+	static std::unique_ptr<MDNSService> registerService(
 		const std::string &serviceName,
 		const std::string &serviceType,
 		const std::string &serviceProto,
@@ -15,7 +17,7 @@ class MDNSService {
 		int servicePort,
 		const std::map<std::string, std::string> txtData
 	);
-	static void unregisterService(void* service);
+	virtual void unregisterService() = 0;
 };
 
 } // namespace bell

+ 11 - 6
components/spotify/cspot/bell/main/platform/apple/MDNSService.cpp

@@ -4,11 +4,20 @@
 
 using namespace bell;
 
+class implMDNSService : public MDNSService {
+private:
+   DNSServiceRef* service;
+
+public:
+   implMDNSService(DNSServiceRef* service) : service(service) { }
+   void unregisterService() { DNSServiceRefDeallocate(*service); }
+};
+
 /**
  * MacOS implementation of MDNSService.
  * @see https://developer.apple.com/documentation/dnssd/1804733-dnsserviceregister
  **/
-void* MDNSService::registerService(
+std::unique_ptr<MDNSService> MDNSService::registerService(
     const std::string& serviceName,
     const std::string& serviceType,
     const std::string& serviceProto,
@@ -37,9 +46,5 @@ void* MDNSService::registerService(
         NULL /* context */
     );
     TXTRecordDeallocate(&txtRecord);
-    return ref;
-}
-
-void MDNSService::unregisterService(void* ref) {
-    DNSServiceRefDeallocate((DNSServiceRef)ref);
+    return std::make_unique<implMDNSService>(ref);
 }

+ 13 - 2
components/spotify/cspot/bell/main/platform/esp/MDNSService.cpp

@@ -5,11 +5,22 @@
 
 using namespace bell;
 
+class implMDNSService : public MDNSService {
+private:
+    const std::string type;
+    const std::string proto;
+    void unregisterService() { mdns_service_remove(type.c_str(), proto.c_str()); }
+
+public:
+   implMDNSService(std::string type, std::string proto) : type(type), proto(proto) { };                        
+};
+
 /**
  * ESP32 implementation of MDNSService
  * @see https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/protocols/mdns.html
  **/
-void* MDNSService::registerService(
+
+std::unique_ptr<MDNSService> MDNSService::registerService(
     const std::string& serviceName,
     const std::string& serviceType,
     const std::string& serviceProto,
@@ -35,5 +46,5 @@ void* MDNSService::registerService(
         txtItems.size() /* num_items */
     );
 
-    return NULL;
+    return std::make_unique<implMDNSService>(serviceType, serviceProto);
 }

+ 66 - 38
components/spotify/cspot/bell/main/platform/linux/MDNSService.cpp

@@ -11,7 +11,7 @@
 #include <avahi-common/alternative.h>
 #include <avahi-common/simple-watch.h>
 #elif !defined(BELL_DISABLE_AVAHI)
-#define BELL_DISABLE_AVAHI    
+#define BELL_DISABLE_AVAHI
 #endif
 
 #include "mdnssvc.h"
@@ -21,19 +21,55 @@
 using namespace bell;
 
 #ifndef BELL_DISABLE_AVAHI
-static AvahiClient *avahiClient = NULL;
-static AvahiSimplePoll *avahiPoll = NULL;
 static void groupHandler(AvahiEntryGroup *g, AvahiEntryGroupState state, AVAHI_GCC_UNUSED void *userdata) { }
 #endif
 
-static in_addr_t host = INADDR_ANY;
-static struct mdnsd *mdnsServer;
+class implMDNSService : public MDNSService {
+private:
+#ifndef BELL_DISABLE_AVAHI
+    AvahiEntryGroup *avahiGroup;
+#endif
+    struct mdns_service* service;
+
+public:
+#ifndef BELL_DISABLE_AVAHI
+    static AvahiClient *avahiClient;
+    static AvahiSimplePoll *avahiPoll;
+#endif
+    static struct mdnsd* mdnsServer;
+    static in_addr_t host;
+
+    implMDNSService(struct mdns_service* service) : service(service) { };
+#ifndef BELL_DISABLE_AVAHI    
+    implMDNSService(AvahiEntryGroup *avahiGroup) : avahiGroup(avahiGroup) { };
+#endif    
+    void unregisterService();
+};
+
+struct mdnsd* implMDNSService::mdnsServer = NULL;
+in_addr_t implMDNSService::host = INADDR_ANY;
+#ifndef BELL_DISABLE_AVAHI
+AvahiClient* implMDNSService::avahiClient = NULL;
+AvahiSimplePoll* implMDNSService::avahiPoll = NULL;
+#endif
 
 /**
  * Linux implementation of MDNSService using avahi.
  * @see https://www.avahi.org/doxygen/html/
  **/
-void* MDNSService::registerService(
+
+void implMDNSService::unregisterService() {
+#ifndef BELL_DISABLE_AVAHI
+    if (avahiGroup) {
+        avahi_entry_group_free(avahiGroup);
+    } else
+#endif
+    {
+        mdns_service_remove(implMDNSService::mdnsServer, service);
+    }
+}
+
+std::unique_ptr<MDNSService> MDNSService::registerService(
     const std::string& serviceName,
     const std::string& serviceType,
     const std::string& serviceProto,
@@ -43,22 +79,22 @@ void* MDNSService::registerService(
 ) {
 #ifndef BELL_DISABLE_AVAHI
     // try avahi first if available
-    if (!avahiPoll) {
-       avahiPoll = avahi_simple_poll_new();
+    if (!implMDNSService::avahiPoll) {
+       implMDNSService::avahiPoll = avahi_simple_poll_new();
     }
 
-    if (avahiPoll && !avahiClient) {
-       avahiClient = avahi_client_new(avahi_simple_poll_get(avahiPoll),
+    if (implMDNSService::avahiPoll && !implMDNSService::avahiClient) {
+       implMDNSService::avahiClient = avahi_client_new(avahi_simple_poll_get(implMDNSService::avahiPoll),
                                       AvahiClientFlags(0), NULL, NULL, NULL);
     }
-
+    
     AvahiEntryGroup *avahiGroup;
 
-    if (avahiClient &&
-        (avahiGroup = avahi_entry_group_new(avahiClient, groupHandler, NULL)) == NULL) {
+    if (implMDNSService::avahiClient &&
+        (avahiGroup = avahi_entry_group_new(implMDNSService::avahiClient, groupHandler, NULL)) == NULL) {
         BELL_LOG(error, "MDNS", "cannot create service %s", serviceName.c_str());
     }
-
+    
     if (avahiGroup) {
         AvahiStringList* avahiTxt = NULL;
 
@@ -80,7 +116,7 @@ void* MDNSService::registerService(
             avahi_entry_group_free(avahiGroup);
         } else {
             BELL_LOG(info, "MDNS", "using avahi for %s", serviceName.c_str());
-            return avahiGroup;
+            return std::make_unique<implMDNSService>(avahiGroup);
         }
     }
 #endif
@@ -92,39 +128,39 @@ void* MDNSService::registerService(
     if (serviceHost.size()) {
         struct hostent *h = gethostbyname(serviceHost.c_str());
         if (h) {
-            memcpy(&host, h->h_addr_list[0], 4);
+            memcpy(&implMDNSService::host, h->h_addr_list[0], 4);
         }
     }
 
     // try go guess ifaddr if we have nothing as listening to INADDR_ANY usually does not work
-	if (host == INADDR_ANY && getifaddrs(&ifaddr) != -1) {
+	if (implMDNSService::host == INADDR_ANY && getifaddrs(&ifaddr) != -1) {
         for (struct ifaddrs* ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
             if (ifa->ifa_addr == NULL || ifa->ifa_addr->sa_family != AF_INET ||
                 !(ifa->ifa_flags & IFF_UP) || !(ifa->ifa_flags & IFF_MULTICAST) ||
                 (ifa->ifa_flags & IFF_LOOPBACK)) continue;
 
-            host = ((struct sockaddr_in*)ifa->ifa_addr)->sin_addr.s_addr;
+            implMDNSService::host = ((struct sockaddr_in*)ifa->ifa_addr)->sin_addr.s_addr;
             break;
         }
         freeifaddrs(ifaddr);
 	}
 
-    if (!mdnsServer) {
+    if (!implMDNSService::mdnsServer) {
         char hostname[256];
         struct in_addr addr;
 
         // it's the same, but who knows..
-        addr.s_addr = host;
+        addr.s_addr = implMDNSService::host;
         gethostname(hostname, sizeof(hostname));
 
-        mdnsServer = mdnsd_start(addr, false);
+        implMDNSService::mdnsServer = mdnsd_start(addr, false);
 
-        if (mdnsServer) {
-            mdnsd_set_hostname(mdnsServer, hostname, addr);
+        if (implMDNSService::mdnsServer) {
+            mdnsd_set_hostname(implMDNSService::mdnsServer, hostname, addr);
         }
     }
 
-    if (mdnsServer) {
+    if (implMDNSService::mdnsServer) {
         std::vector<const char*> txt;
         std::vector<std::unique_ptr<std::string>> txtStr;
 
@@ -137,25 +173,17 @@ void* MDNSService::registerService(
         txt.push_back(NULL);
         std::string type(serviceType + "." + serviceProto + ".local");
 
-        BELL_LOG(info, "MDNS", "using build-in mDNS for %s", serviceName.c_str());
-        struct mdns_service* mdnsService = mdnsd_register_svc(mdnsServer, serviceName.c_str(),
+        BELL_LOG(info, "MDNS", "using built-in mDNS for %s", serviceName.c_str());
+        struct mdns_service* mdnsService = mdnsd_register_svc(implMDNSService::mdnsServer, serviceName.c_str(),
                                                               type.c_str(), servicePort, NULL, txt.data());
         if (mdnsService) {
-            return mdnsService;
+            auto service = mdnsd_register_svc(implMDNSService::mdnsServer, serviceName.c_str(),
+                                      type.c_str(), servicePort, NULL, txt.data());
+
+            return std::make_unique<implMDNSService>(service);
         }
     }
 
     BELL_LOG(error, "MDNS", "cannot start any mDNS listener for %s", serviceName.c_str());
     return NULL;
 }
-
-void MDNSService::unregisterService(void* service) {
-#ifndef BELL_DISABLE_AVAHI
-    if (avahiClient) {
-        avahi_entry_group_free((AvahiEntryGroup*)service);
-    } else         
-#endif
-    {
-        mdns_service_remove(mdnsServer, (mdns_service*)service);
-    }
-}

+ 20 - 10
components/spotify/cspot/bell/main/platform/win32/MDNSService.cpp

@@ -17,13 +17,23 @@
 
 using namespace bell;
 
-static struct mdnsd *mdnsService;
+class implMDNSService : public MDNSService {
+private:
+    struct mdns_service* service;
+    void unregisterService(void) { mdns_service_remove(implMDNSService::mdnsServer, service); };
+    
+public:
+    static struct mdnsd* mdnsServer;
+    implMDNSService(struct mdns_service* service) : service(service) { };
+};
 
 /**
  * Win32 implementation of MDNSService
  **/
 
-void* MDNSService::registerService(
+struct mdnsd* implMDNSService::mdnsServer = NULL;
+
+std::unique_ptr<MDNSService> MDNSService::registerService(
     const std::string& serviceName,
     const std::string& serviceType,
     const std::string& serviceProto,
@@ -31,7 +41,7 @@ void* MDNSService::registerService(
     int servicePort,
     const std::map<std::string, std::string> txtData
 ) {
-    if (!mdnsService) {
+    if (!implMDNSService::mdnsServer) {
         char hostname[128];
         gethostname(hostname, sizeof(hostname));
 
@@ -49,14 +59,14 @@ void* MDNSService::registerService(
                 if (adapter->FirstGatewayAddress && unicast->Address.lpSockaddr->sa_family == AF_INET) {
                     host = (struct sockaddr_in*)unicast->Address.lpSockaddr;
                     BELL_LOG(info, "mdns", "mDNS on interface %s", inet_ntoa(host->sin_addr));
-                    mdnsService = mdnsd_start(host->sin_addr, false);
+                    implMDNSService::mdnsServer = mdnsd_start(host->sin_addr, false);
                     break;
                 }
             }
         }
 
-        assert(mdnsService);
-        mdnsd_set_hostname(mdnsService, hostname, host->sin_addr);
+        assert(implMDNSService::mdnsServer);
+        mdnsd_set_hostname(implMDNSService::mdnsServer, hostname, host->sin_addr);
         free(adapters);
     }
 
@@ -71,9 +81,9 @@ void* MDNSService::registerService(
     txt.push_back(NULL);
 
     std::string type(serviceType + "." + serviceProto + ".local");
-    return mdnsd_register_svc(mdnsService, serviceName.c_str(), type.c_str(), servicePort, NULL, txt.data());
-}
 
-void MDNSService::unregisterService(void* service) {
-    mdns_service_remove(mdnsService, (mdns_service*)service);
+    auto service = mdnsd_register_svc(implMDNSService::mdnsServer, serviceName.c_str(), 
+                                      type.c_str(), servicePort, NULL, txt.data());
+
+    return std::make_unique<implMDNSService>(service);
 }

+ 0 - 1
components/spotify/cspot/bell/main/utilities/include/BellUtils.h

@@ -11,7 +11,6 @@
 #include <vector>
 
 #ifdef ESP_PLATFORM
-//#include "esp_mac.h"
 #include "esp_system.h"
 #endif
 

+ 1 - 1
components/spotify/cspot/bell/main/utilities/include/Crypto.h

@@ -22,7 +22,7 @@ extern "C" {
 
 #define DH_KEY_SIZE 96
 
-static unsigned char DHPrime[] = {
+const static unsigned char DHPrime[] = {
     /* Well-known Group 1, 768-bit prime */
     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc9,
     0x0f, 0xda, 0xa2, 0x21, 0x68, 0xc2, 0x34, 0xc4, 0xc6,

+ 4 - 0
components/spotify/cspot/include/ApResolve.h

@@ -4,7 +4,11 @@
 #include <string>
 
 #include "HTTPClient.h"
+#ifdef BELL_ONLY_CJSON
+#include "cJSON.h"
+#else
 #include "nlohmann/json.hpp"
+#endif
 
 namespace cspot {
 class ApResolve {

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

@@ -10,7 +10,6 @@
 #include "CSpotContext.h"
 #include "AccessKeyFetcher.h"
 
-
 namespace cspot {
 
 class CDNTrackStream {

+ 2 - 0
components/spotify/cspot/include/LoginBlob.h

@@ -3,7 +3,9 @@
 #include <iostream>
 #include <map>
 #include <memory>
+#ifndef BELL_ONLY_CJSON
 #include <nlohmann/json.hpp>
+#endif
 #include <vector>
 
 #include "ConstantParameters.h"

+ 11 - 0
components/spotify/cspot/src/AccessKeyFetcher.cpp

@@ -41,13 +41,24 @@ void AccessKeyFetcher::getAccessKey(AccessKeyFetcher::Callback callback) {
         if (res.fail) return;
         char* accessKeyJson = (char*)res.parts[0].data();
         auto accessJSON = std::string(accessKeyJson, strrchr(accessKeyJson, '}') - accessKeyJson + 1);
+#ifdef BELL_ONLY_CJSON
+        cJSON* jsonBody = cJSON_Parse(accessJSON.c_str());
+        this->accessKey = cJSON_GetObjectItem(jsonBody, "accessToken")->valuestring;
+        int expiresIn = cJSON_GetObjectItem(jsonBody, "expiresIn")->valueint;
+#else
         auto jsonBody = nlohmann::json::parse(accessJSON);
         this->accessKey = jsonBody["accessToken"];
         int expiresIn = jsonBody["expiresIn"];
+#endif        
         expiresIn = expiresIn / 2;  // Refresh token before it expires
 
         this->expiresAt =
             timeProvider->getSyncedTimestamp() + (expiresIn * 1000);
+#ifdef BELL_ONLY_CJSON
+        callback(cJSON_GetObjectItem(jsonBody, "accessToken")->valuestring);
+        cJSON_Delete(jsonBody);
+#else            
         callback(jsonBody["accessToken"]);
+#endif    
       });
 }

+ 7 - 0
components/spotify/cspot/src/ApResolve.cpp

@@ -18,6 +18,13 @@ std::string ApResolve::fetchFirstApAddress()
     std::string_view responseStr = request->body();
 
     // parse json with nlohmann
+#if BELL_ONLY_CJSON
+   cJSON* json = cJSON_Parse(responseStr.data());
+   auto ap_string = std::string(cJSON_GetArrayItem(cJSON_GetObjectItem(json, "ap_list"), 0)->valuestring);
+   cJSON_Delete(json);
+   return ap_string;
+#else    
     auto json = nlohmann::json::parse(responseStr);
     return json["ap_list"][0];
+#endif    
 }

+ 8 - 2
components/spotify/cspot/src/CDNTrackStream.cpp

@@ -38,8 +38,14 @@ void CDNTrackStream::fetchFile(const std::vector<uint8_t>& trackId,
 
     std::string_view result = req->body();
 
+#ifdef BELL_ONLY_CJSON
+    cJSON* jsonResult = cJSON_Parse(result.data());
+    std::string cdnUrl = cJSON_GetArrayItem(cJSON_GetObjectItem(jsonResult, "cdnurl"), 0)->valuestring;
+    cJSON_Delete(jsonResult);
+#else
     auto jsonResult = nlohmann::json::parse(result);
     std::string cdnUrl = jsonResult["cdnurl"][0];
+#endif
     if (this->status != Status::FAILED) {
 
       this->cdnUrl = cdnUrl;
@@ -61,7 +67,7 @@ void CDNTrackStream::seek(size_t newPos) {
   this->position = newPos;
 }
 
-void CDNTrackStream::openStream() {
+void CDNTrackStream::openStream() {  
   CSPOT_LOG(info, "Opening HTTP stream to %s", this->cdnUrl.c_str());
 
   // Open connection, read first 128 bytes
@@ -96,7 +102,7 @@ void CDNTrackStream::openStream() {
   this->isConnected = true;
 }
 
-size_t CDNTrackStream::readBytes(uint8_t* dst, size_t bytes) {
+size_t CDNTrackStream::readBytes(uint8_t* dst, size_t bytes) {  
   size_t offsetPosition = position + SPOTIFY_OPUS_HEADER;
   size_t actualFileSize = this->totalFileSize + SPOTIFY_OPUS_HEADER;
 

+ 58 - 1
components/spotify/cspot/src/LoginBlob.cpp

@@ -1,6 +1,9 @@
 #include "LoginBlob.h"
 #include "ConstantParameters.h"
 #include "Logger.h"
+#ifdef BELL_ONLY_CJSON
+#include "cJSON.h"
+#endif
 
 using namespace cspot;
 
@@ -125,21 +128,46 @@ void LoginBlob::loadUserPass(const std::string& username,
 }
 
 void LoginBlob::loadJson(const std::string& json) {
+#ifdef BELL_ONLY_CJSON
+  cJSON* root = cJSON_Parse(json.c_str());
+  cJSON* item = cJSON_GetObjectItem(root, "authType");
+  this->authType = item->valueint;
+  item = cJSON_GetObjectItem(root, "username");
+  this->username = item->valuestring;
+  item = cJSON_GetObjectItem(root, "authData");
+  std::string authDataObject = item->valuestring;
+  cJSON_Delete(root);
+#else
   auto root = nlohmann::json::parse(json);
   this->authType = root["authType"];
   this->username = root["username"];
   std::string authDataObject = root["authData"];
 
   this->authData = crypto->base64Decode(authDataObject);
+#endif  
 }
 
 std::string LoginBlob::toJson() {
+#ifdef BELL_ONLY_CJSON
+  cJSON* json_obj = cJSON_CreateObject();  
+  cJSON_AddStringToObject(json_obj, "authData", crypto->base64Encode(authData).c_str());
+  cJSON_AddNumberToObject(json_obj, "authType", this->authType);
+  cJSON_AddStringToObject(json_obj, "username", this->username.c_str());
+  
+  char *str = cJSON_PrintUnformatted(json_obj);
+  cJSON_Delete(json_obj); 
+  std::string json_objStr(str);
+  free(str);
+  
+  return json_objStr;
+#else  
   nlohmann::json obj;
   obj["authData"] = crypto->base64Encode(authData);
   obj["authType"] = this->authType;
   obj["username"] = this->username;
 
   return obj.dump();
+#endif
 }
 
 void LoginBlob::loadZeroconfQuery(
@@ -164,7 +192,35 @@ std::string LoginBlob::buildZeroconfInfo() {
   // Encode publicKey into base64
 
   auto encodedKey = crypto->base64Encode(crypto->publicKey);
-
+#ifdef BELL_ONLY_CJSON
+  cJSON* json_obj = cJSON_CreateObject();  
+  cJSON_AddNumberToObject(json_obj, "status", 101);
+  cJSON_AddStringToObject(json_obj, "statusString", "OK");
+  cJSON_AddStringToObject(json_obj, "version", cspot::protocolVersion);
+  cJSON_AddStringToObject(json_obj, "libraryVersion", cspot::swVersion);
+  cJSON_AddStringToObject(json_obj, "accountReq", "PREMIUM");
+  cJSON_AddStringToObject(json_obj, "brandDisplayName", cspot::brandName);
+  cJSON_AddStringToObject(json_obj, "modelDisplayName", name.c_str());
+  cJSON_AddStringToObject(json_obj, "voiceSupport", "NO");
+  cJSON_AddStringToObject(json_obj, "availability", this->username.c_str());
+  cJSON_AddNumberToObject(json_obj, "productID", 0);
+  cJSON_AddStringToObject(json_obj, "tokenType", "default");
+  cJSON_AddStringToObject(json_obj, "groupStatus", "NONE");
+  cJSON_AddStringToObject(json_obj, "resolverVersion", "0");
+  cJSON_AddStringToObject(json_obj, "scope", "streaming,client-authorization-universal");  
+  cJSON_AddStringToObject(json_obj, "activeUser", "");  
+  cJSON_AddStringToObject(json_obj, "deviceID", deviceId.c_str());  
+  cJSON_AddStringToObject(json_obj, "remoteName", name.c_str());  
+  cJSON_AddStringToObject(json_obj, "publicKey", encodedKey.c_str());  
+  cJSON_AddStringToObject(json_obj, "deviceType", "deviceType");  
+  
+  char *str = cJSON_PrintUnformatted(json_obj);
+  cJSON_Delete(json_obj); 
+  std::string json_objStr(str);
+  free(str);
+  
+  return json_objStr;
+#else 
   nlohmann::json obj;
   obj["status"] = 101;
   obj["statusString"] = "OK";
@@ -188,6 +244,7 @@ std::string LoginBlob::buildZeroconfInfo() {
   obj["deviceType"] = "SPEAKER";
 
   return obj.dump();
+#endif
 }
 
 std::string LoginBlob::getDeviceId() {

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

@@ -150,7 +150,7 @@ void TrackPlayer::runTask() {
                 dataCallback(pcmBuffer.data() + (ret - toWrite), toWrite,
                              this->currentTrackStream->trackInfo.trackId, this->sequence);
             if (written == 0) {
-              BELL_SLEEP_MS(10);
+              BELL_SLEEP_MS(50);
             }
             toWrite -= written;
           }

+ 1 - 1
components/spotify/cspot_sink.c

@@ -135,7 +135,7 @@ static bool cmd_handler(cspot_event_t event, ...) {
 	case CSPOT_SEEK:
 		displayer_timer(DISPLAYER_ELAPSED, va_arg(args, int), -1);
 		break;
-	case CSPOT_TRACK: {
+	case CSPOT_TRACK_INFO: {
 		uint32_t duration = va_arg(args, int);
         uint32_t offset = va_arg(args, int);
 		char *artist = va_arg(args, char*), *album = va_arg(args, char*), *title = va_arg(args, char*);

+ 5 - 2
components/spotify/cspot_sink.h

@@ -16,8 +16,11 @@ extern "C"
 #endif
 
 // STOP means remove playlist, FLUSH means flush audio buffer, DISC means bye-bye
-typedef enum { 	CSPOT_START, CSPOT_DISC, CSPOT_FLUSH, CSPOT_STOP, CSPOT_PLAY, CSPOT_PAUSE, CSPOT_SEEK, CSPOT_TRACK, 
-				CSPOT_VOLUME, CSPOT_VOLUME_UP, CSPOT_VOLUME_DOWN, CSPOT_NEXT, CSPOT_PREV, CSPOT_TOGGLE, CSPOT_REMAINING,
+typedef enum { 	CSPOT_START, CSPOT_DISC, CSPOT_FLUSH, CSPOT_STOP, CSPOT_PLAY, CSPOT_PAUSE, CSPOT_SEEK, 
+                CSPOT_NEXT, CSPOT_PREV, CSPOT_TOGGLE, 
+                CSPOT_TRACK_INFO, CSPOT_TRACK_MARK,
+				CSPOT_VOLUME, CSPOT_VOLUME_UP, CSPOT_VOLUME_DOWN, 
+                CSPOT_QUERY_STARTED, CSPOT_QUERY_REMAINING, 
 } cspot_event_t;
 				
 typedef bool (*cspot_cmd_cb_t)(cspot_event_t event, ...);				

+ 13 - 1
components/squeezelite/decode_external.c

@@ -351,6 +351,8 @@ static bool cspot_cmd_handler(cspot_event_t cmd, va_list args)
 		output.current_sample_rate = output.next_sample_rate = va_arg(args, u32_t);
 		output.external = DECODE_CSPOT;
 		output.frames_played = 0;
+        // in 1/10 of seconds
+        output.threshold = 25;
 		output.state = OUTPUT_STOPPED;
         sink_state = SINK_ABORT;
 		_buf_flush(outputbuf);
@@ -386,10 +388,20 @@ static bool cspot_cmd_handler(cspot_event_t cmd, va_list args)
 		output.stop_time = gettime_ms();
 		LOG_INFO("CSpot pause");
 		break;
-    case CSPOT_REMAINING: {
+    case CSPOT_TRACK_MARK:
+        output.track_start = outputbuf->writep;
+        break;
+    case CSPOT_QUERY_REMAINING: {
         uint32_t *remaining = va_arg(args, uint32_t*);
         *remaining = (_buf_used(outputbuf) * 1000) / (output.current_sample_rate * BYTES_PER_FRAME);
         break;      
+    }
+    case CSPOT_QUERY_STARTED: {
+        uint32_t *started = va_arg(args, uint32_t*);
+        *started = output.track_started;
+        // this is a read_and_clear event
+        output.track_started = false;
+        break;      
     }
 	case CSPOT_VOLUME: {
 		u32_t volume = va_arg(args, u32_t);