From fc78b36c1f14216c0795787219a3cb2cacf1ae03 Mon Sep 17 00:00:00 2001 From: philippe44 Date: Mon, 27 Mar 2023 17:09:27 -0700 Subject: [PATCH] make it fit in allocated space --- components/spotify/CMakeLists.txt | 8 +- components/spotify/Shim.cpp | 234 +++++++----------- components/spotify/cspot/bell/CMakeLists.txt | 51 ++-- .../spotify/cspot/bell/main/io/URLParser.cpp | 39 ++- .../cspot/bell/main/io/include/HTTPClient.h | 10 + .../cspot/bell/main/io/include/URLParser.h | 13 +- .../cspot/bell/main/platform/MDNSService.h | 8 +- .../bell/main/platform/apple/MDNSService.cpp | 17 +- .../bell/main/platform/esp/MDNSService.cpp | 15 +- .../bell/main/platform/linux/MDNSService.cpp | 104 +++++--- .../bell/main/platform/win32/MDNSService.cpp | 30 ++- .../bell/main/utilities/include/BellUtils.h | 1 - .../bell/main/utilities/include/Crypto.h | 2 +- components/spotify/cspot/include/ApResolve.h | 4 + .../spotify/cspot/include/CDNTrackStream.h | 1 - components/spotify/cspot/include/LoginBlob.h | 2 + .../spotify/cspot/src/AccessKeyFetcher.cpp | 11 + components/spotify/cspot/src/ApResolve.cpp | 7 + .../spotify/cspot/src/CDNTrackStream.cpp | 10 +- components/spotify/cspot/src/LoginBlob.cpp | 59 ++++- components/spotify/cspot/src/TrackPlayer.cpp | 2 +- components/spotify/cspot_sink.c | 2 +- components/spotify/cspot_sink.h | 7 +- components/squeezelite/decode_external.c | 14 +- 24 files changed, 421 insertions(+), 230 deletions(-) diff --git a/components/spotify/CMakeLists.txt b/components/spotify/CMakeLists.txt index 29bfe688..1eaa59df 100644 --- a/components/spotify/CMakeLists.txt +++ b/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") diff --git a/components/spotify/Shim.cpp b/components/spotify/Shim.cpp index 35698005..230b36aa 100644 --- a/components/spotify/Shim.cpp +++ b/components/spotify/Shim.cpp @@ -29,40 +29,7 @@ #include "platform_config.h" #include "tools.h" -//#include "time.h" - -/* -#include -#include -#include -#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 -#include -#include -#include -#include -#include -#include -#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 isRunning = true; std::atomic isPaused = true; - chunkManager(std::shared_ptr centralAudioBuffer, std::function trackHandler, std::function audioHandler); + chunkManager(std::shared_ptr centralAudioBuffer, std::function trackHandler, + std::function dataHandler); void teardown(); private: std::shared_ptr centralAudioBuffer; std::function trackHandler; - std::function audioHandler; + std::function dataHandler; std::mutex runningMutex; void runTask() override; }; chunkManager::chunkManager(std::shared_ptr centralAudioBuffer, - std::function trackHandler, std::function audioHandler) - : bell::Task("player", 4 * 1024, 0, 0) { + std::function trackHandler, std::function 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 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 blob; std::unique_ptr spirc; @@ -156,30 +121,28 @@ private: void runTask(); public: - std::atomic trackNotify = false; + typedef enum {TRACK_INIT, TRACK_NOTIFY, TRACK_STREAM, TRACK_END} TrackStatus; + std::atomic 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 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(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 event } case cspot::SpircHandler::EventType::TRACK_INFO: { auto trackInfo = std::get(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 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 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 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(32); + + centralAudioBuffer = std::make_shared(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(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; - } -*/ - + player->command(event); return true; } diff --git a/components/spotify/cspot/bell/CMakeLists.txt b/components/spotify/cspot/bell/CMakeLists.txt index fdc9ec22..80af580b 100644 --- a/components/spotify/cspot/bell/CMakeLists.txt +++ b/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() diff --git a/components/spotify/cspot/bell/main/io/URLParser.cpp b/components/spotify/cspot/bell/main/io/URLParser.cpp index 1bc1f1f9..e26d28e8 100644 --- a/components/spotify/cspot/bell/main/io/URLParser.cpp +++ b/components/spotify/cspot/bell/main/io/URLParser.cpp @@ -1 +1,38 @@ -#include "URLParser.h" \ No newline at end of file +#include "URLParser.h" + +namespace bell { + +#ifdef BELL_DISABLE_REGEX +void URLParser::parse(const char* url, std::vector& 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 +} diff --git a/components/spotify/cspot/bell/main/io/include/HTTPClient.h b/components/spotify/cspot/bell/main/io/include/HTTPClient.h index afb6fcd5..31dc3a7c 100644 --- a/components/spotify/cspot/bell/main/io/include/HTTPClient.h +++ b/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 } }; diff --git a/components/spotify/cspot/bell/main/io/include/URLParser.h b/components/spotify/cspot/bell/main/io/include/URLParser.h index b850d1c4..3778c91b 100644 --- a/components/spotify/cspot/bell/main/io/include/URLParser.h +++ b/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& 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 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"); diff --git a/components/spotify/cspot/bell/main/platform/MDNSService.h b/components/spotify/cspot/bell/main/platform/MDNSService.h index 0c16f21d..f7389a41 100644 --- a/components/spotify/cspot/bell/main/platform/MDNSService.h +++ b/components/spotify/cspot/bell/main/platform/MDNSService.h @@ -2,12 +2,14 @@ #include #include +#include namespace bell { class MDNSService { - public: - static void* registerService( +public: + virtual ~MDNSService() { } + static std::unique_ptr registerService( const std::string &serviceName, const std::string &serviceType, const std::string &serviceProto, @@ -15,7 +17,7 @@ class MDNSService { int servicePort, const std::map txtData ); - static void unregisterService(void* service); + virtual void unregisterService() = 0; }; } // namespace bell \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/platform/apple/MDNSService.cpp b/components/spotify/cspot/bell/main/platform/apple/MDNSService.cpp index 3efba1a1..1948302f 100644 --- a/components/spotify/cspot/bell/main/platform/apple/MDNSService.cpp +++ b/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::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(ref); } \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/platform/esp/MDNSService.cpp b/components/spotify/cspot/bell/main/platform/esp/MDNSService.cpp index 418b419d..81f269cf 100644 --- a/components/spotify/cspot/bell/main/platform/esp/MDNSService.cpp +++ b/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::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(serviceType, serviceProto); } diff --git a/components/spotify/cspot/bell/main/platform/linux/MDNSService.cpp b/components/spotify/cspot/bell/main/platform/linux/MDNSService.cpp index 9e1811e9..0109890b 100644 --- a/components/spotify/cspot/bell/main/platform/linux/MDNSService.cpp +++ b/components/spotify/cspot/bell/main/platform/linux/MDNSService.cpp @@ -11,7 +11,7 @@ #include #include #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::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(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 txt; std::vector> 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(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); - } -} diff --git a/components/spotify/cspot/bell/main/platform/win32/MDNSService.cpp b/components/spotify/cspot/bell/main/platform/win32/MDNSService.cpp index 33ce2d2a..fb91061a 100644 --- a/components/spotify/cspot/bell/main/platform/win32/MDNSService.cpp +++ b/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::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 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(service); } diff --git a/components/spotify/cspot/bell/main/utilities/include/BellUtils.h b/components/spotify/cspot/bell/main/utilities/include/BellUtils.h index 0dffd29b..ac6921cd 100644 --- a/components/spotify/cspot/bell/main/utilities/include/BellUtils.h +++ b/components/spotify/cspot/bell/main/utilities/include/BellUtils.h @@ -11,7 +11,6 @@ #include #ifdef ESP_PLATFORM -//#include "esp_mac.h" #include "esp_system.h" #endif diff --git a/components/spotify/cspot/bell/main/utilities/include/Crypto.h b/components/spotify/cspot/bell/main/utilities/include/Crypto.h index 368f209e..bafb5f07 100644 --- a/components/spotify/cspot/bell/main/utilities/include/Crypto.h +++ b/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, diff --git a/components/spotify/cspot/include/ApResolve.h b/components/spotify/cspot/include/ApResolve.h index bfcee94d..6ebc7755 100644 --- a/components/spotify/cspot/include/ApResolve.h +++ b/components/spotify/cspot/include/ApResolve.h @@ -4,7 +4,11 @@ #include #include "HTTPClient.h" +#ifdef BELL_ONLY_CJSON +#include "cJSON.h" +#else #include "nlohmann/json.hpp" +#endif namespace cspot { class ApResolve { diff --git a/components/spotify/cspot/include/CDNTrackStream.h b/components/spotify/cspot/include/CDNTrackStream.h index 658c13c8..3375a578 100644 --- a/components/spotify/cspot/include/CDNTrackStream.h +++ b/components/spotify/cspot/include/CDNTrackStream.h @@ -10,7 +10,6 @@ #include "CSpotContext.h" #include "AccessKeyFetcher.h" - namespace cspot { class CDNTrackStream { diff --git a/components/spotify/cspot/include/LoginBlob.h b/components/spotify/cspot/include/LoginBlob.h index 2cb91709..e6a539ba 100644 --- a/components/spotify/cspot/include/LoginBlob.h +++ b/components/spotify/cspot/include/LoginBlob.h @@ -3,7 +3,9 @@ #include #include #include +#ifndef BELL_ONLY_CJSON #include +#endif #include #include "ConstantParameters.h" diff --git a/components/spotify/cspot/src/AccessKeyFetcher.cpp b/components/spotify/cspot/src/AccessKeyFetcher.cpp index 87263d08..4856610f 100644 --- a/components/spotify/cspot/src/AccessKeyFetcher.cpp +++ b/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 }); } diff --git a/components/spotify/cspot/src/ApResolve.cpp b/components/spotify/cspot/src/ApResolve.cpp index 89b0c54f..277fed84 100644 --- a/components/spotify/cspot/src/ApResolve.cpp +++ b/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 } diff --git a/components/spotify/cspot/src/CDNTrackStream.cpp b/components/spotify/cspot/src/CDNTrackStream.cpp index 5ffdd41d..7b3fda7d 100644 --- a/components/spotify/cspot/src/CDNTrackStream.cpp +++ b/components/spotify/cspot/src/CDNTrackStream.cpp @@ -38,8 +38,14 @@ void CDNTrackStream::fetchFile(const std::vector& 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; diff --git a/components/spotify/cspot/src/LoginBlob.cpp b/components/spotify/cspot/src/LoginBlob.cpp index 9d92fe3f..4a86ef39 100644 --- a/components/spotify/cspot/src/LoginBlob.cpp +++ b/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() { diff --git a/components/spotify/cspot/src/TrackPlayer.cpp b/components/spotify/cspot/src/TrackPlayer.cpp index 829d4f5c..2acd4f1b 100644 --- a/components/spotify/cspot/src/TrackPlayer.cpp +++ b/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; } diff --git a/components/spotify/cspot_sink.c b/components/spotify/cspot_sink.c index ac6434bd..e1fd20c4 100644 --- a/components/spotify/cspot_sink.c +++ b/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*); diff --git a/components/spotify/cspot_sink.h b/components/spotify/cspot_sink.h index 5f6d5179..31cc529b 100644 --- a/components/spotify/cspot_sink.h +++ b/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, ...); diff --git a/components/squeezelite/decode_external.c b/components/squeezelite/decode_external.c index c72c46ab..27f90b33 100644 --- a/components/squeezelite/decode_external.c +++ b/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);