mirror of
https://github.com/sle118/squeezelite-esp32.git
synced 2025-12-07 03:57:07 +03:00
make it fit in allocated space
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
# this must be set *before* idf_component_register
|
# this must be set *before* idf_component_register
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
|
|
||||||
idf_component_register(
|
idf_component_register(
|
||||||
SRC_DIRS .
|
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_CODECS ON)
|
||||||
set(BELL_DISABLE_SINKS 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)
|
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_VORBIS "idf::codecs" CACHE STRING "provide own codecs")
|
||||||
set(BELL_EXTERNAL_CJSON "idf::json" CACHE STRING "provide own CJSON")
|
set(BELL_EXTERNAL_CJSON "idf::json" CACHE STRING "provide own CJSON")
|
||||||
|
|
||||||
|
|||||||
@@ -29,40 +29,7 @@
|
|||||||
#include "platform_config.h"
|
#include "platform_config.h"
|
||||||
#include "tools.h"
|
#include "tools.h"
|
||||||
|
|
||||||
//#include "time.h"
|
static class cspotPlayer *player;
|
||||||
|
|
||||||
/*
|
|
||||||
#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;
|
|
||||||
|
|
||||||
/****************************************************************************************
|
/****************************************************************************************
|
||||||
* Chunk manager class (task)
|
* Chunk manager class (task)
|
||||||
@@ -72,27 +39,33 @@ class chunkManager : public bell::Task {
|
|||||||
public:
|
public:
|
||||||
std::atomic<bool> isRunning = true;
|
std::atomic<bool> isRunning = true;
|
||||||
std::atomic<bool> isPaused = 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();
|
void teardown();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::shared_ptr<bell::CentralAudioBuffer> centralAudioBuffer;
|
std::shared_ptr<bell::CentralAudioBuffer> centralAudioBuffer;
|
||||||
std::function<void()> trackHandler;
|
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;
|
std::mutex runningMutex;
|
||||||
|
|
||||||
void runTask() override;
|
void runTask() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
chunkManager::chunkManager(std::shared_ptr<bell::CentralAudioBuffer> centralAudioBuffer,
|
chunkManager::chunkManager(std::shared_ptr<bell::CentralAudioBuffer> centralAudioBuffer,
|
||||||
std::function<void()> trackHandler, std::function<void(const uint8_t*, size_t)> audioHandler)
|
std::function<void()> trackHandler, std::function<void(const uint8_t*, size_t)> dataHandler)
|
||||||
: bell::Task("player", 4 * 1024, 0, 0) {
|
: bell::Task("chunker", 4 * 1024, 0, 0) {
|
||||||
this->centralAudioBuffer = centralAudioBuffer;
|
this->centralAudioBuffer = centralAudioBuffer;
|
||||||
this->trackHandler = trackHandler;
|
this->trackHandler = trackHandler;
|
||||||
this->audioHandler = audioHandler;
|
this->dataHandler = dataHandler;
|
||||||
startTask();
|
startTask();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void chunkManager::teardown() {
|
||||||
|
isRunning = false;
|
||||||
|
std::scoped_lock lock(runningMutex);
|
||||||
|
}
|
||||||
|
|
||||||
void chunkManager::runTask() {
|
void chunkManager::runTask() {
|
||||||
std::scoped_lock lock(runningMutex);
|
std::scoped_lock lock(runningMutex);
|
||||||
size_t lastHash = 0;
|
size_t lastHash = 0;
|
||||||
@@ -118,15 +91,10 @@ void chunkManager::runTask() {
|
|||||||
trackHandler();
|
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
|
* Player's main class & task
|
||||||
*/
|
*/
|
||||||
@@ -134,17 +102,14 @@ void chunkManager::teardown() {
|
|||||||
class cspotPlayer : public bell::Task {
|
class cspotPlayer : public bell::Task {
|
||||||
private:
|
private:
|
||||||
std::string name;
|
std::string name;
|
||||||
bool playback = false;
|
|
||||||
bell::WrappedSemaphore clientConnected;
|
bell::WrappedSemaphore clientConnected;
|
||||||
std::shared_ptr<bell::CentralAudioBuffer> centralAudioBuffer;
|
std::shared_ptr<bell::CentralAudioBuffer> centralAudioBuffer;
|
||||||
|
|
||||||
TimerHandle_t trackTimer;
|
|
||||||
|
|
||||||
int startOffset, volume = 0, bitrate = 160;
|
int startOffset, volume = 0, bitrate = 160;
|
||||||
httpd_handle_t serverHandle;
|
httpd_handle_t serverHandle;
|
||||||
int serverPort;
|
int serverPort;
|
||||||
cspot_cmd_cb_t cmdHandler;
|
cspot_cmd_cb_t cmdHandler;
|
||||||
cspot_data_cb_t dataHandler;
|
cspot_data_cb_t dataHandler;
|
||||||
|
|
||||||
std::shared_ptr<cspot::LoginBlob> blob;
|
std::shared_ptr<cspot::LoginBlob> blob;
|
||||||
std::unique_ptr<cspot::SpircHandler> spirc;
|
std::unique_ptr<cspot::SpircHandler> spirc;
|
||||||
@@ -156,30 +121,28 @@ private:
|
|||||||
void runTask();
|
void runTask();
|
||||||
|
|
||||||
public:
|
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(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 handleGET(httpd_req_t *request);
|
||||||
esp_err_t handlePOST(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) :
|
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),
|
bell::Task("playerInstance", 32 * 1024, 0, 0),
|
||||||
serverHandle(server), serverPort(port),
|
serverHandle(server), serverPort(port),
|
||||||
cmdHandler(cmdHandler), dataHandler(dataHandler) {
|
cmdHandler(cmdHandler), dataHandler(dataHandler) {
|
||||||
|
|
||||||
cJSON *item, *config = config_alloc_get_cjson("cspot_config");
|
cJSON *item, *config = config_alloc_get_cjson("cspot_config");
|
||||||
if ((item = cJSON_GetObjectItem(config, "volume")) != NULL) volume = item->valueint;
|
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, "bitrate")) != NULL) bitrate = item->valueint;
|
||||||
if ((item = cJSON_GetObjectItem(config, "deviceName") ) != NULL) this->name = item->valuestring;
|
if ((item = cJSON_GetObjectItem(config, "deviceName") ) != NULL) this->name = item->valuestring;
|
||||||
else this->name = name;
|
else this->name = name;
|
||||||
cJSON_Delete(config);
|
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" {
|
extern "C" {
|
||||||
@@ -190,10 +153,6 @@ extern "C" {
|
|||||||
static esp_err_t handlePOST(httpd_req_t *request) {
|
static esp_err_t handlePOST(httpd_req_t *request) {
|
||||||
return player->handlePOST(request);
|
return player->handlePOST(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void trackTimerHandler(TimerHandle_t xTimer) {
|
|
||||||
player->trackNotify = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t cspotPlayer::handleGET(httpd_req_t *request) {
|
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_AddNumberToObject(response, "status", 101);
|
||||||
cJSON_AddStringToObject(response, "statusString", "ERROR-OK");
|
cJSON_AddStringToObject(response, "statusString", "ERROR-OK");
|
||||||
cJSON_AddNumberToObject(response, "spotifyError", 0);
|
cJSON_AddNumberToObject(response, "spotifyError", 0);
|
||||||
|
|
||||||
// get body if any (add '\0' at the end if used as string)
|
// get body if any (add '\0' at the end if used as string)
|
||||||
if (request->content_len) {
|
if (request->content_len) {
|
||||||
char* body = (char*) calloc(1, request->content_len + 1);
|
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));
|
esp_err_t rc = httpd_resp_send(request, responseStr, strlen(responseStr));
|
||||||
free(responseStr);
|
free(responseStr);
|
||||||
|
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,18 +217,14 @@ void cspotPlayer::eventHandler(std::unique_ptr<cspot::SpircHandler::Event> event
|
|||||||
centralAudioBuffer->clearBuffer();
|
centralAudioBuffer->clearBuffer();
|
||||||
|
|
||||||
// we are not playing anymore
|
// we are not playing anymore
|
||||||
xTimerStop(trackTimer, portMAX_DELAY);
|
trackStatus = TRACK_INIT;
|
||||||
trackNotify = false;
|
|
||||||
playback = false;
|
|
||||||
|
|
||||||
// memorize position for when track's beginning will be detected
|
// memorize position for when track's beginning will be detected
|
||||||
startOffset = std::get<int>(event->data);
|
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
|
// Spotify servers do not send volume at connection
|
||||||
spirc->setRemoteVolume(volume);
|
spirc->setRemoteVolume(volume);
|
||||||
|
|
||||||
|
cmdHandler(CSPOT_START, 44100);
|
||||||
|
CSPOT_LOG(info, "restart");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case cspot::SpircHandler::EventType::PLAY_PAUSE: {
|
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: {
|
case cspot::SpircHandler::EventType::TRACK_INFO: {
|
||||||
auto trackInfo = std::get<cspot::CDNTrackStream::TrackInfo>(event->data);
|
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());
|
trackInfo.album.c_str(), trackInfo.name.c_str(), trackInfo.imageUrl.c_str());
|
||||||
spirc->updatePositionMs(startOffset);
|
spirc->updatePositionMs(startOffset);
|
||||||
startOffset = 0;
|
startOffset = 0;
|
||||||
@@ -296,7 +251,6 @@ void cspotPlayer::eventHandler(std::unique_ptr<cspot::SpircHandler::Event> event
|
|||||||
}
|
}
|
||||||
case cspot::SpircHandler::EventType::DISC:
|
case cspot::SpircHandler::EventType::DISC:
|
||||||
centralAudioBuffer->clearBuffer();
|
centralAudioBuffer->clearBuffer();
|
||||||
xTimerStop(trackTimer, portMAX_DELAY);
|
|
||||||
cmdHandler(CSPOT_DISC);
|
cmdHandler(CSPOT_DISC);
|
||||||
chunker->teardown();
|
chunker->teardown();
|
||||||
break;
|
break;
|
||||||
@@ -306,6 +260,7 @@ void cspotPlayer::eventHandler(std::unique_ptr<cspot::SpircHandler::Event> event
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case cspot::SpircHandler::EventType::DEPLETED:
|
case cspot::SpircHandler::EventType::DEPLETED:
|
||||||
|
trackStatus = TRACK_END;
|
||||||
CSPOT_LOG(info, "playlist ended, no track left to play");
|
CSPOT_LOG(info, "playlist ended, no track left to play");
|
||||||
break;
|
break;
|
||||||
case cspot::SpircHandler::EventType::VOLUME:
|
case cspot::SpircHandler::EventType::VOLUME:
|
||||||
@@ -318,18 +273,38 @@ void cspotPlayer::eventHandler(std::unique_ptr<cspot::SpircHandler::Event> event
|
|||||||
}
|
}
|
||||||
|
|
||||||
void cspotPlayer::trackHandler(void) {
|
void cspotPlayer::trackHandler(void) {
|
||||||
if (playback) {
|
// this is just informative
|
||||||
uint32_t remains;
|
auto trackInfo = spirc->getTrackPlayer()->getCurrentTrackInfo();
|
||||||
auto trackInfo = spirc->getTrackPlayer()->getCurrentTrackInfo();
|
uint32_t remains;
|
||||||
// if this is not first track, estimate when the current one will finish
|
cmdHandler(CSPOT_QUERY_REMAINING, &remains);
|
||||||
cmdHandler(CSPOT_REMAINING, &remains);
|
CSPOT_LOG(info, "next track <%s> will play in %d ms", trackInfo.name.c_str(), remains);
|
||||||
if (remains > 100) xTimerChangePeriod(trackTimer, pdMS_TO_TICKS(remains), portMAX_DELAY);
|
|
||||||
else trackNotify = true;
|
// inform sink of track beginning
|
||||||
CSPOT_LOG(info, "next track <%s> in cspot buffers, remaining %d ms", trackInfo.name.c_str(), remains);
|
trackStatus = TRACK_NOTIFY;
|
||||||
} else {
|
cmdHandler(CSPOT_TRACK_MARK);
|
||||||
trackNotify = true;
|
}
|
||||||
playback = true;
|
|
||||||
}
|
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() {
|
void cspotPlayer::runTask() {
|
||||||
@@ -352,27 +327,26 @@ void cspotPlayer::runTask() {
|
|||||||
// Register mdns service, for spotify to find us
|
// Register mdns service, for spotify to find us
|
||||||
bell::MDNSService::registerService( blob->getDeviceName(), "_spotify-connect", "_tcp", "", serverPort,
|
bell::MDNSService::registerService( blob->getDeviceName(), "_spotify-connect", "_tcp", "", serverPort,
|
||||||
{ {"VERSION", "1.0"}, {"CPath", "/spotify_info"}, {"Stack", "SP"} });
|
{ {"VERSION", "1.0"}, {"CPath", "/spotify_info"}, {"Stack", "SP"} });
|
||||||
|
|
||||||
static int count = 0;
|
static int count = 0;
|
||||||
// gone with the wind...
|
// gone with the wind...
|
||||||
while (1) {
|
while (1) {
|
||||||
clientConnected.wait();
|
clientConnected.wait();
|
||||||
|
|
||||||
CSPOT_LOG(info, "Spotify client connected for %s", name.c_str());
|
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);
|
auto ctx = cspot::Context::createFromBlob(blob);
|
||||||
|
|
||||||
if (bitrate == 320) ctx->config.audioFormat = AudioFormat_OGG_VORBIS_320;
|
if (bitrate == 320) ctx->config.audioFormat = AudioFormat_OGG_VORBIS_320;
|
||||||
else if (bitrate == 96) ctx->config.audioFormat = AudioFormat_OGG_VORBIS_96;
|
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();
|
ctx->session->connectWithRandomAp();
|
||||||
auto token = ctx->session->authenticate(blob);
|
auto token = ctx->session->authenticate(blob);
|
||||||
|
|
||||||
// Auth successful
|
// Auth successful
|
||||||
if (token.size() > 0) {
|
if (token.size() > 0) {
|
||||||
trackTimer = xTimerCreate("trackTimer", pdMS_TO_TICKS(1000), pdFALSE, NULL, trackTimerHandler);
|
|
||||||
spirc = std::make_unique<cspot::SpircHandler>(ctx);
|
spirc = std::make_unique<cspot::SpircHandler>(ctx);
|
||||||
|
|
||||||
// set call back to calculate a hash on trackId
|
// set call back to calculate a hash on trackId
|
||||||
@@ -398,32 +372,50 @@ void cspotPlayer::runTask() {
|
|||||||
[this](const uint8_t* data, size_t bytes) {
|
[this](const uint8_t* data, size_t bytes) {
|
||||||
return dataHandler(data, bytes);
|
return dataHandler(data, bytes);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// set volume at connection
|
||||||
|
cmdHandler(CSPOT_VOLUME, volume);
|
||||||
|
|
||||||
// exit when player has stopped (received a DISC)
|
// exit when player has stopped (received a DISC)
|
||||||
while (chunker->isRunning) {
|
while (chunker->isRunning) {
|
||||||
ctx->session->handlePacket();
|
ctx->session->handlePacket();
|
||||||
|
|
||||||
// inform Spotify that next track has started (don't need to be super accurate)
|
// low-accuracy polling events
|
||||||
if (trackNotify) {
|
if (trackStatus == TRACK_NOTIFY) {
|
||||||
CSPOT_LOG(info, "next track's audio has reached DAC");
|
// inform Spotify that next track has started (don't need to be super accurate)
|
||||||
spirc->notifyAudioReachedPlayback();
|
uint32_t started;
|
||||||
trackNotify = false;
|
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->disconnect();
|
||||||
|
spirc.reset();
|
||||||
|
|
||||||
CSPOT_LOG(info, "disconnecting player %s", name.c_str());
|
CSPOT_LOG(info, "disconnecting player %s", name.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
// we want to release memory ASAP and fore sure
|
// we want to release memory ASAP and fore sure
|
||||||
centralAudioBuffer.reset();
|
centralAudioBuffer.reset();
|
||||||
ctx.reset();
|
ctx.reset();
|
||||||
token.clear();
|
token.clear();
|
||||||
|
|
||||||
// update volume when we disconnect
|
// 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_DeleteItemFromObject(config, "volume");
|
||||||
cJSON_AddNumberToObject(config, "volume", volume);
|
cJSON_AddNumberToObject(config, "volume", volume);
|
||||||
config_set_cjson_str_and_free("cspot_config", config);
|
config_set_cjson_str_and_free("cspot_config", config);
|
||||||
@@ -433,7 +425,7 @@ void cspotPlayer::runTask() {
|
|||||||
/****************************************************************************************
|
/****************************************************************************************
|
||||||
* API to create and start a cspot instance
|
* 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) {
|
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();
|
bell::setDefaultLogger();
|
||||||
player = new cspotPlayer(name, server, port, cmd_cb, data_cb);
|
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
|
* Commands sent by local buttons/actions
|
||||||
*/
|
*/
|
||||||
|
|
||||||
bool cspot_cmd(struct cspot_s* ctx, cspot_event_t event, void *param) {
|
bool cspot_cmd(struct cspot_s* ctx, cspot_event_t event, void *param) {
|
||||||
// we might have no controller left
|
player->command(event);
|
||||||
/*
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,9 +17,17 @@ option(BELL_SINK_ALSA "Enable ALSA audio sink" OFF)
|
|||||||
option(BELL_SINK_PORTAUDIO "Enable PortAudio sink" OFF)
|
option(BELL_SINK_PORTAUDIO "Enable PortAudio sink" OFF)
|
||||||
|
|
||||||
# cJSON wrapper
|
# 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")
|
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
|
# disable json tests
|
||||||
set(JSON_BuildTests OFF CACHE INTERNAL "")
|
set(JSON_BuildTests OFF CACHE INTERNAL "")
|
||||||
|
|
||||||
@@ -46,13 +54,16 @@ if(NOT BELL_DISABLE_CODECS)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
message(STATUS " Disable built-in audio sinks: ${BELL_DISABLE_SINKS}")
|
message(STATUS " Disable built-in audio sinks: ${BELL_DISABLE_SINKS}")
|
||||||
|
message(STATUS " Use Vorbis float version: ${BELL_VORBIS_FLOAT}")
|
||||||
|
|
||||||
if(NOT BELL_DISABLE_SINKS)
|
if(NOT BELL_DISABLE_SINKS)
|
||||||
message(STATUS " - ALSA sink: ${BELL_SINK_ALSA}")
|
message(STATUS " - ALSA sink: ${BELL_SINK_ALSA}")
|
||||||
message(STATUS " - PortAudio sink: ${BELL_SINK_PORTAUDIO}")
|
message(STATUS " - PortAudio sink: ${BELL_SINK_PORTAUDIO}")
|
||||||
endif()
|
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
|
# Include nanoPB library
|
||||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/external/nanopb/extra")
|
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")
|
list(REMOVE_ITEM SOURCES "${IO_DIR}/EncodedAudioStream.cpp")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(BELL_EXTERNAL_VORBIS)
|
if(NOT BELL_EXTERNAL_VORBIS STREQUAL "")
|
||||||
message(STATUS "Using external Vorbis codec ${BELL_EXTERNAL_VORBIS}")
|
message(STATUS "Using external Vorbis codec ${BELL_EXTERNAL_VORBIS}")
|
||||||
list(APPEND EXTRA_LIBS ${BELL_EXTERNAL_VORBIS})
|
list(APPEND EXTRA_LIBS ${BELL_EXTERNAL_VORBIS})
|
||||||
else()
|
else()
|
||||||
@@ -254,21 +265,19 @@ if(NOT BELL_DISABLE_SINKS)
|
|||||||
list(APPEND SOURCES ${SINK_SOURCES})
|
list(APPEND SOURCES ${SINK_SOURCES})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(BELL_DISABLE_CJSON)
|
if(NOT BELL_ONLY_CJSON)
|
||||||
list(REMOVE_ITEM SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/main/io/JSONObject.cpp")
|
|
||||||
else()
|
|
||||||
add_subdirectory(external/nlohmann_json)
|
add_subdirectory(external/nlohmann_json)
|
||||||
list(APPEND EXTRA_LIBS nlohmann_json::nlohmann_json)
|
list(APPEND EXTRA_LIBS nlohmann_json::nlohmann_json)
|
||||||
if(BELL_EXTERNAL_CJSON)
|
endif()
|
||||||
list(APPEND EXTRA_LIBS ${BELL_EXTERNAL_CJSON})
|
|
||||||
else()
|
|
||||||
list(APPEND SOURCES "external/cJSON/cJSON.c")
|
|
||||||
list(APPEND EXTRA_INCLUDES "external/cJSON")
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (BELL_DISABLE_FMT)
|
if(BELL_EXTERNAL_CJSON)
|
||||||
else()
|
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")
|
list(APPEND EXTRA_INCLUDES "external/fmt/include")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@@ -306,6 +315,18 @@ if(BELL_VORBIS_FLOAT)
|
|||||||
target_compile_definitions(bell PUBLIC BELL_VORBIS_FLOAT)
|
target_compile_definitions(bell PUBLIC BELL_VORBIS_FLOAT)
|
||||||
endif()
|
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")
|
if(WIN32 OR CMAKE_SYSTEM_NAME STREQUAL "SunOS")
|
||||||
target_compile_definitions(bell PUBLIC PB_NO_STATIC_ASSERT)
|
target_compile_definitions(bell PUBLIC PB_NO_STATIC_ASSERT)
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,7 +15,9 @@
|
|||||||
#include "ByteStream.h"
|
#include "ByteStream.h"
|
||||||
#include "SocketStream.h"
|
#include "SocketStream.h"
|
||||||
#include "URLParser.h"
|
#include "URLParser.h"
|
||||||
|
#ifndef BELL_DISABLE_FMT
|
||||||
#include "fmt/core.h"
|
#include "fmt/core.h"
|
||||||
|
#endif
|
||||||
#include "picohttpparser.h"
|
#include "picohttpparser.h"
|
||||||
|
|
||||||
namespace bell {
|
namespace bell {
|
||||||
@@ -29,11 +31,19 @@ class HTTPClient {
|
|||||||
// Helper over ValueHeader, formatting a HTTP bytes range
|
// Helper over ValueHeader, formatting a HTTP bytes range
|
||||||
struct RangeHeader {
|
struct RangeHeader {
|
||||||
static ValueHeader range(int32_t from, int32_t to) {
|
static ValueHeader range(int32_t from, int32_t to) {
|
||||||
|
#ifndef BELL_DISABLE_FMT
|
||||||
return ValueHeader{"Range", fmt::format("bytes={}-{}", from, to)};
|
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) {
|
static ValueHeader last(int32_t nbytes) {
|
||||||
|
#ifndef BELL_DISABLE_FMT
|
||||||
return ValueHeader{"Range", fmt::format("bytes=-{}", nbytes)};
|
return ValueHeader{"Range", fmt::format("bytes=-{}", nbytes)};
|
||||||
|
#else
|
||||||
|
return ValueHeader{"Range", "bytes=-" + std::to_string(nbytes)};
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -58,16 +58,23 @@ class URLParser {
|
|||||||
|
|
||||||
std::string schema = "http";
|
std::string schema = "http";
|
||||||
std::string path;
|
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) {
|
static URLParser parse(const std::string& url) {
|
||||||
URLParser parser;
|
URLParser parser;
|
||||||
|
|
||||||
// apply parser.urlParseRegex to url
|
// 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::cmatch match;
|
||||||
|
|
||||||
std::regex_match(url.c_str(), match, parser.urlParseRegex);
|
std::regex_match(url.c_str(), match, parser.urlParseRegex);
|
||||||
|
#endif
|
||||||
|
|
||||||
if (match.size() < 3) {
|
if (match.size() < 3) {
|
||||||
throw std::invalid_argument("Invalid URL");
|
throw std::invalid_argument("Invalid URL");
|
||||||
|
|||||||
@@ -2,12 +2,14 @@
|
|||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
namespace bell {
|
namespace bell {
|
||||||
|
|
||||||
class MDNSService {
|
class MDNSService {
|
||||||
public:
|
public:
|
||||||
static void* registerService(
|
virtual ~MDNSService() { }
|
||||||
|
static std::unique_ptr<MDNSService> registerService(
|
||||||
const std::string &serviceName,
|
const std::string &serviceName,
|
||||||
const std::string &serviceType,
|
const std::string &serviceType,
|
||||||
const std::string &serviceProto,
|
const std::string &serviceProto,
|
||||||
@@ -15,7 +17,7 @@ class MDNSService {
|
|||||||
int servicePort,
|
int servicePort,
|
||||||
const std::map<std::string, std::string> txtData
|
const std::map<std::string, std::string> txtData
|
||||||
);
|
);
|
||||||
static void unregisterService(void* service);
|
virtual void unregisterService() = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace bell
|
} // namespace bell
|
||||||
@@ -4,11 +4,20 @@
|
|||||||
|
|
||||||
using namespace bell;
|
using namespace bell;
|
||||||
|
|
||||||
|
class implMDNSService : public MDNSService {
|
||||||
|
private:
|
||||||
|
DNSServiceRef* service;
|
||||||
|
|
||||||
|
public:
|
||||||
|
implMDNSService(DNSServiceRef* service) : service(service) { }
|
||||||
|
void unregisterService() { DNSServiceRefDeallocate(*service); }
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MacOS implementation of MDNSService.
|
* MacOS implementation of MDNSService.
|
||||||
* @see https://developer.apple.com/documentation/dnssd/1804733-dnsserviceregister
|
* @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& serviceName,
|
||||||
const std::string& serviceType,
|
const std::string& serviceType,
|
||||||
const std::string& serviceProto,
|
const std::string& serviceProto,
|
||||||
@@ -37,9 +46,5 @@ void* MDNSService::registerService(
|
|||||||
NULL /* context */
|
NULL /* context */
|
||||||
);
|
);
|
||||||
TXTRecordDeallocate(&txtRecord);
|
TXTRecordDeallocate(&txtRecord);
|
||||||
return ref;
|
return std::make_unique<implMDNSService>(ref);
|
||||||
}
|
|
||||||
|
|
||||||
void MDNSService::unregisterService(void* ref) {
|
|
||||||
DNSServiceRefDeallocate((DNSServiceRef)ref);
|
|
||||||
}
|
}
|
||||||
@@ -5,11 +5,22 @@
|
|||||||
|
|
||||||
using namespace bell;
|
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
|
* ESP32 implementation of MDNSService
|
||||||
* @see https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/protocols/mdns.html
|
* @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& serviceName,
|
||||||
const std::string& serviceType,
|
const std::string& serviceType,
|
||||||
const std::string& serviceProto,
|
const std::string& serviceProto,
|
||||||
@@ -35,5 +46,5 @@ void* MDNSService::registerService(
|
|||||||
txtItems.size() /* num_items */
|
txtItems.size() /* num_items */
|
||||||
);
|
);
|
||||||
|
|
||||||
return NULL;
|
return std::make_unique<implMDNSService>(serviceType, serviceProto);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
#include <avahi-common/alternative.h>
|
#include <avahi-common/alternative.h>
|
||||||
#include <avahi-common/simple-watch.h>
|
#include <avahi-common/simple-watch.h>
|
||||||
#elif !defined(BELL_DISABLE_AVAHI)
|
#elif !defined(BELL_DISABLE_AVAHI)
|
||||||
#define BELL_DISABLE_AVAHI
|
#define BELL_DISABLE_AVAHI
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "mdnssvc.h"
|
#include "mdnssvc.h"
|
||||||
@@ -21,19 +21,55 @@
|
|||||||
using namespace bell;
|
using namespace bell;
|
||||||
|
|
||||||
#ifndef BELL_DISABLE_AVAHI
|
#ifndef BELL_DISABLE_AVAHI
|
||||||
static AvahiClient *avahiClient = NULL;
|
|
||||||
static AvahiSimplePoll *avahiPoll = NULL;
|
|
||||||
static void groupHandler(AvahiEntryGroup *g, AvahiEntryGroupState state, AVAHI_GCC_UNUSED void *userdata) { }
|
static void groupHandler(AvahiEntryGroup *g, AvahiEntryGroupState state, AVAHI_GCC_UNUSED void *userdata) { }
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static in_addr_t host = INADDR_ANY;
|
class implMDNSService : public MDNSService {
|
||||||
static struct mdnsd *mdnsServer;
|
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.
|
* Linux implementation of MDNSService using avahi.
|
||||||
* @see https://www.avahi.org/doxygen/html/
|
* @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& serviceName,
|
||||||
const std::string& serviceType,
|
const std::string& serviceType,
|
||||||
const std::string& serviceProto,
|
const std::string& serviceProto,
|
||||||
@@ -43,22 +79,22 @@ void* MDNSService::registerService(
|
|||||||
) {
|
) {
|
||||||
#ifndef BELL_DISABLE_AVAHI
|
#ifndef BELL_DISABLE_AVAHI
|
||||||
// try avahi first if available
|
// try avahi first if available
|
||||||
if (!avahiPoll) {
|
if (!implMDNSService::avahiPoll) {
|
||||||
avahiPoll = avahi_simple_poll_new();
|
implMDNSService::avahiPoll = avahi_simple_poll_new();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (avahiPoll && !avahiClient) {
|
if (implMDNSService::avahiPoll && !implMDNSService::avahiClient) {
|
||||||
avahiClient = avahi_client_new(avahi_simple_poll_get(avahiPoll),
|
implMDNSService::avahiClient = avahi_client_new(avahi_simple_poll_get(implMDNSService::avahiPoll),
|
||||||
AvahiClientFlags(0), NULL, NULL, NULL);
|
AvahiClientFlags(0), NULL, NULL, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
AvahiEntryGroup *avahiGroup;
|
AvahiEntryGroup *avahiGroup;
|
||||||
|
|
||||||
if (avahiClient &&
|
if (implMDNSService::avahiClient &&
|
||||||
(avahiGroup = avahi_entry_group_new(avahiClient, groupHandler, NULL)) == NULL) {
|
(avahiGroup = avahi_entry_group_new(implMDNSService::avahiClient, groupHandler, NULL)) == NULL) {
|
||||||
BELL_LOG(error, "MDNS", "cannot create service %s", serviceName.c_str());
|
BELL_LOG(error, "MDNS", "cannot create service %s", serviceName.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (avahiGroup) {
|
if (avahiGroup) {
|
||||||
AvahiStringList* avahiTxt = NULL;
|
AvahiStringList* avahiTxt = NULL;
|
||||||
|
|
||||||
@@ -80,7 +116,7 @@ void* MDNSService::registerService(
|
|||||||
avahi_entry_group_free(avahiGroup);
|
avahi_entry_group_free(avahiGroup);
|
||||||
} else {
|
} else {
|
||||||
BELL_LOG(info, "MDNS", "using avahi for %s", serviceName.c_str());
|
BELL_LOG(info, "MDNS", "using avahi for %s", serviceName.c_str());
|
||||||
return avahiGroup;
|
return std::make_unique<implMDNSService>(avahiGroup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -92,39 +128,39 @@ void* MDNSService::registerService(
|
|||||||
if (serviceHost.size()) {
|
if (serviceHost.size()) {
|
||||||
struct hostent *h = gethostbyname(serviceHost.c_str());
|
struct hostent *h = gethostbyname(serviceHost.c_str());
|
||||||
if (h) {
|
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
|
// 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) {
|
for (struct ifaddrs* ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
|
||||||
if (ifa->ifa_addr == NULL || ifa->ifa_addr->sa_family != AF_INET ||
|
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_UP) || !(ifa->ifa_flags & IFF_MULTICAST) ||
|
||||||
(ifa->ifa_flags & IFF_LOOPBACK)) continue;
|
(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;
|
break;
|
||||||
}
|
}
|
||||||
freeifaddrs(ifaddr);
|
freeifaddrs(ifaddr);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mdnsServer) {
|
if (!implMDNSService::mdnsServer) {
|
||||||
char hostname[256];
|
char hostname[256];
|
||||||
struct in_addr addr;
|
struct in_addr addr;
|
||||||
|
|
||||||
// it's the same, but who knows..
|
// it's the same, but who knows..
|
||||||
addr.s_addr = host;
|
addr.s_addr = implMDNSService::host;
|
||||||
gethostname(hostname, sizeof(hostname));
|
gethostname(hostname, sizeof(hostname));
|
||||||
|
|
||||||
mdnsServer = mdnsd_start(addr, false);
|
implMDNSService::mdnsServer = mdnsd_start(addr, false);
|
||||||
|
|
||||||
if (mdnsServer) {
|
if (implMDNSService::mdnsServer) {
|
||||||
mdnsd_set_hostname(mdnsServer, hostname, addr);
|
mdnsd_set_hostname(implMDNSService::mdnsServer, hostname, addr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mdnsServer) {
|
if (implMDNSService::mdnsServer) {
|
||||||
std::vector<const char*> txt;
|
std::vector<const char*> txt;
|
||||||
std::vector<std::unique_ptr<std::string>> txtStr;
|
std::vector<std::unique_ptr<std::string>> txtStr;
|
||||||
|
|
||||||
@@ -137,25 +173,17 @@ void* MDNSService::registerService(
|
|||||||
txt.push_back(NULL);
|
txt.push_back(NULL);
|
||||||
std::string type(serviceType + "." + serviceProto + ".local");
|
std::string type(serviceType + "." + serviceProto + ".local");
|
||||||
|
|
||||||
BELL_LOG(info, "MDNS", "using build-in mDNS for %s", serviceName.c_str());
|
BELL_LOG(info, "MDNS", "using built-in mDNS for %s", serviceName.c_str());
|
||||||
struct mdns_service* mdnsService = mdnsd_register_svc(mdnsServer, serviceName.c_str(),
|
struct mdns_service* mdnsService = mdnsd_register_svc(implMDNSService::mdnsServer, serviceName.c_str(),
|
||||||
type.c_str(), servicePort, NULL, txt.data());
|
type.c_str(), servicePort, NULL, txt.data());
|
||||||
if (mdnsService) {
|
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());
|
BELL_LOG(error, "MDNS", "cannot start any mDNS listener for %s", serviceName.c_str());
|
||||||
return NULL;
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -17,13 +17,23 @@
|
|||||||
|
|
||||||
using namespace bell;
|
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
|
* 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& serviceName,
|
||||||
const std::string& serviceType,
|
const std::string& serviceType,
|
||||||
const std::string& serviceProto,
|
const std::string& serviceProto,
|
||||||
@@ -31,7 +41,7 @@ void* MDNSService::registerService(
|
|||||||
int servicePort,
|
int servicePort,
|
||||||
const std::map<std::string, std::string> txtData
|
const std::map<std::string, std::string> txtData
|
||||||
) {
|
) {
|
||||||
if (!mdnsService) {
|
if (!implMDNSService::mdnsServer) {
|
||||||
char hostname[128];
|
char hostname[128];
|
||||||
gethostname(hostname, sizeof(hostname));
|
gethostname(hostname, sizeof(hostname));
|
||||||
|
|
||||||
@@ -49,14 +59,14 @@ void* MDNSService::registerService(
|
|||||||
if (adapter->FirstGatewayAddress && unicast->Address.lpSockaddr->sa_family == AF_INET) {
|
if (adapter->FirstGatewayAddress && unicast->Address.lpSockaddr->sa_family == AF_INET) {
|
||||||
host = (struct sockaddr_in*)unicast->Address.lpSockaddr;
|
host = (struct sockaddr_in*)unicast->Address.lpSockaddr;
|
||||||
BELL_LOG(info, "mdns", "mDNS on interface %s", inet_ntoa(host->sin_addr));
|
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(mdnsService);
|
assert(implMDNSService::mdnsServer);
|
||||||
mdnsd_set_hostname(mdnsService, hostname, host->sin_addr);
|
mdnsd_set_hostname(implMDNSService::mdnsServer, hostname, host->sin_addr);
|
||||||
free(adapters);
|
free(adapters);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,9 +81,9 @@ void* MDNSService::registerService(
|
|||||||
txt.push_back(NULL);
|
txt.push_back(NULL);
|
||||||
|
|
||||||
std::string type(serviceType + "." + serviceProto + ".local");
|
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) {
|
auto service = mdnsd_register_svc(implMDNSService::mdnsServer, serviceName.c_str(),
|
||||||
mdns_service_remove(mdnsService, (mdns_service*)service);
|
type.c_str(), servicePort, NULL, txt.data());
|
||||||
|
|
||||||
|
return std::make_unique<implMDNSService>(service);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#ifdef ESP_PLATFORM
|
#ifdef ESP_PLATFORM
|
||||||
//#include "esp_mac.h"
|
|
||||||
#include "esp_system.h"
|
#include "esp_system.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ extern "C" {
|
|||||||
|
|
||||||
#define DH_KEY_SIZE 96
|
#define DH_KEY_SIZE 96
|
||||||
|
|
||||||
static unsigned char DHPrime[] = {
|
const static unsigned char DHPrime[] = {
|
||||||
/* Well-known Group 1, 768-bit prime */
|
/* Well-known Group 1, 768-bit prime */
|
||||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc9,
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc9,
|
||||||
0x0f, 0xda, 0xa2, 0x21, 0x68, 0xc2, 0x34, 0xc4, 0xc6,
|
0x0f, 0xda, 0xa2, 0x21, 0x68, 0xc2, 0x34, 0xc4, 0xc6,
|
||||||
|
|||||||
@@ -4,7 +4,11 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "HTTPClient.h"
|
#include "HTTPClient.h"
|
||||||
|
#ifdef BELL_ONLY_CJSON
|
||||||
|
#include "cJSON.h"
|
||||||
|
#else
|
||||||
#include "nlohmann/json.hpp"
|
#include "nlohmann/json.hpp"
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace cspot {
|
namespace cspot {
|
||||||
class ApResolve {
|
class ApResolve {
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
#include "CSpotContext.h"
|
#include "CSpotContext.h"
|
||||||
#include "AccessKeyFetcher.h"
|
#include "AccessKeyFetcher.h"
|
||||||
|
|
||||||
|
|
||||||
namespace cspot {
|
namespace cspot {
|
||||||
|
|
||||||
class CDNTrackStream {
|
class CDNTrackStream {
|
||||||
|
|||||||
@@ -3,7 +3,9 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#ifndef BELL_ONLY_CJSON
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
#endif
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "ConstantParameters.h"
|
#include "ConstantParameters.h"
|
||||||
|
|||||||
@@ -41,13 +41,24 @@ void AccessKeyFetcher::getAccessKey(AccessKeyFetcher::Callback callback) {
|
|||||||
if (res.fail) return;
|
if (res.fail) return;
|
||||||
char* accessKeyJson = (char*)res.parts[0].data();
|
char* accessKeyJson = (char*)res.parts[0].data();
|
||||||
auto accessJSON = std::string(accessKeyJson, strrchr(accessKeyJson, '}') - accessKeyJson + 1);
|
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);
|
auto jsonBody = nlohmann::json::parse(accessJSON);
|
||||||
this->accessKey = jsonBody["accessToken"];
|
this->accessKey = jsonBody["accessToken"];
|
||||||
int expiresIn = jsonBody["expiresIn"];
|
int expiresIn = jsonBody["expiresIn"];
|
||||||
|
#endif
|
||||||
expiresIn = expiresIn / 2; // Refresh token before it expires
|
expiresIn = expiresIn / 2; // Refresh token before it expires
|
||||||
|
|
||||||
this->expiresAt =
|
this->expiresAt =
|
||||||
timeProvider->getSyncedTimestamp() + (expiresIn * 1000);
|
timeProvider->getSyncedTimestamp() + (expiresIn * 1000);
|
||||||
|
#ifdef BELL_ONLY_CJSON
|
||||||
|
callback(cJSON_GetObjectItem(jsonBody, "accessToken")->valuestring);
|
||||||
|
cJSON_Delete(jsonBody);
|
||||||
|
#else
|
||||||
callback(jsonBody["accessToken"]);
|
callback(jsonBody["accessToken"]);
|
||||||
|
#endif
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,13 @@ std::string ApResolve::fetchFirstApAddress()
|
|||||||
std::string_view responseStr = request->body();
|
std::string_view responseStr = request->body();
|
||||||
|
|
||||||
// parse json with nlohmann
|
// 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);
|
auto json = nlohmann::json::parse(responseStr);
|
||||||
return json["ap_list"][0];
|
return json["ap_list"][0];
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,8 +38,14 @@ void CDNTrackStream::fetchFile(const std::vector<uint8_t>& trackId,
|
|||||||
|
|
||||||
std::string_view result = req->body();
|
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);
|
auto jsonResult = nlohmann::json::parse(result);
|
||||||
std::string cdnUrl = jsonResult["cdnurl"][0];
|
std::string cdnUrl = jsonResult["cdnurl"][0];
|
||||||
|
#endif
|
||||||
if (this->status != Status::FAILED) {
|
if (this->status != Status::FAILED) {
|
||||||
|
|
||||||
this->cdnUrl = cdnUrl;
|
this->cdnUrl = cdnUrl;
|
||||||
@@ -61,7 +67,7 @@ void CDNTrackStream::seek(size_t newPos) {
|
|||||||
this->position = newPos;
|
this->position = newPos;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CDNTrackStream::openStream() {
|
void CDNTrackStream::openStream() {
|
||||||
CSPOT_LOG(info, "Opening HTTP stream to %s", this->cdnUrl.c_str());
|
CSPOT_LOG(info, "Opening HTTP stream to %s", this->cdnUrl.c_str());
|
||||||
|
|
||||||
// Open connection, read first 128 bytes
|
// Open connection, read first 128 bytes
|
||||||
@@ -96,7 +102,7 @@ void CDNTrackStream::openStream() {
|
|||||||
this->isConnected = true;
|
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 offsetPosition = position + SPOTIFY_OPUS_HEADER;
|
||||||
size_t actualFileSize = this->totalFileSize + SPOTIFY_OPUS_HEADER;
|
size_t actualFileSize = this->totalFileSize + SPOTIFY_OPUS_HEADER;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
#include "LoginBlob.h"
|
#include "LoginBlob.h"
|
||||||
#include "ConstantParameters.h"
|
#include "ConstantParameters.h"
|
||||||
#include "Logger.h"
|
#include "Logger.h"
|
||||||
|
#ifdef BELL_ONLY_CJSON
|
||||||
|
#include "cJSON.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
using namespace cspot;
|
using namespace cspot;
|
||||||
|
|
||||||
@@ -125,21 +128,46 @@ void LoginBlob::loadUserPass(const std::string& username,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void LoginBlob::loadJson(const std::string& json) {
|
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);
|
auto root = nlohmann::json::parse(json);
|
||||||
this->authType = root["authType"];
|
this->authType = root["authType"];
|
||||||
this->username = root["username"];
|
this->username = root["username"];
|
||||||
std::string authDataObject = root["authData"];
|
std::string authDataObject = root["authData"];
|
||||||
|
|
||||||
this->authData = crypto->base64Decode(authDataObject);
|
this->authData = crypto->base64Decode(authDataObject);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string LoginBlob::toJson() {
|
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;
|
nlohmann::json obj;
|
||||||
obj["authData"] = crypto->base64Encode(authData);
|
obj["authData"] = crypto->base64Encode(authData);
|
||||||
obj["authType"] = this->authType;
|
obj["authType"] = this->authType;
|
||||||
obj["username"] = this->username;
|
obj["username"] = this->username;
|
||||||
|
|
||||||
return obj.dump();
|
return obj.dump();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void LoginBlob::loadZeroconfQuery(
|
void LoginBlob::loadZeroconfQuery(
|
||||||
@@ -164,7 +192,35 @@ std::string LoginBlob::buildZeroconfInfo() {
|
|||||||
// Encode publicKey into base64
|
// Encode publicKey into base64
|
||||||
|
|
||||||
auto encodedKey = crypto->base64Encode(crypto->publicKey);
|
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;
|
nlohmann::json obj;
|
||||||
obj["status"] = 101;
|
obj["status"] = 101;
|
||||||
obj["statusString"] = "OK";
|
obj["statusString"] = "OK";
|
||||||
@@ -188,6 +244,7 @@ std::string LoginBlob::buildZeroconfInfo() {
|
|||||||
obj["deviceType"] = "SPEAKER";
|
obj["deviceType"] = "SPEAKER";
|
||||||
|
|
||||||
return obj.dump();
|
return obj.dump();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string LoginBlob::getDeviceId() {
|
std::string LoginBlob::getDeviceId() {
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ void TrackPlayer::runTask() {
|
|||||||
dataCallback(pcmBuffer.data() + (ret - toWrite), toWrite,
|
dataCallback(pcmBuffer.data() + (ret - toWrite), toWrite,
|
||||||
this->currentTrackStream->trackInfo.trackId, this->sequence);
|
this->currentTrackStream->trackInfo.trackId, this->sequence);
|
||||||
if (written == 0) {
|
if (written == 0) {
|
||||||
BELL_SLEEP_MS(10);
|
BELL_SLEEP_MS(50);
|
||||||
}
|
}
|
||||||
toWrite -= written;
|
toWrite -= written;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ static bool cmd_handler(cspot_event_t event, ...) {
|
|||||||
case CSPOT_SEEK:
|
case CSPOT_SEEK:
|
||||||
displayer_timer(DISPLAYER_ELAPSED, va_arg(args, int), -1);
|
displayer_timer(DISPLAYER_ELAPSED, va_arg(args, int), -1);
|
||||||
break;
|
break;
|
||||||
case CSPOT_TRACK: {
|
case CSPOT_TRACK_INFO: {
|
||||||
uint32_t duration = va_arg(args, int);
|
uint32_t duration = va_arg(args, int);
|
||||||
uint32_t offset = 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*);
|
char *artist = va_arg(args, char*), *album = va_arg(args, char*), *title = va_arg(args, char*);
|
||||||
|
|||||||
@@ -16,8 +16,11 @@ extern "C"
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// STOP means remove playlist, FLUSH means flush audio buffer, DISC means bye-bye
|
// 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,
|
typedef enum { CSPOT_START, CSPOT_DISC, CSPOT_FLUSH, CSPOT_STOP, CSPOT_PLAY, CSPOT_PAUSE, CSPOT_SEEK,
|
||||||
CSPOT_VOLUME, CSPOT_VOLUME_UP, CSPOT_VOLUME_DOWN, CSPOT_NEXT, CSPOT_PREV, CSPOT_TOGGLE, CSPOT_REMAINING,
|
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;
|
} cspot_event_t;
|
||||||
|
|
||||||
typedef bool (*cspot_cmd_cb_t)(cspot_event_t event, ...);
|
typedef bool (*cspot_cmd_cb_t)(cspot_event_t event, ...);
|
||||||
|
|||||||
@@ -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.current_sample_rate = output.next_sample_rate = va_arg(args, u32_t);
|
||||||
output.external = DECODE_CSPOT;
|
output.external = DECODE_CSPOT;
|
||||||
output.frames_played = 0;
|
output.frames_played = 0;
|
||||||
|
// in 1/10 of seconds
|
||||||
|
output.threshold = 25;
|
||||||
output.state = OUTPUT_STOPPED;
|
output.state = OUTPUT_STOPPED;
|
||||||
sink_state = SINK_ABORT;
|
sink_state = SINK_ABORT;
|
||||||
_buf_flush(outputbuf);
|
_buf_flush(outputbuf);
|
||||||
@@ -386,10 +388,20 @@ static bool cspot_cmd_handler(cspot_event_t cmd, va_list args)
|
|||||||
output.stop_time = gettime_ms();
|
output.stop_time = gettime_ms();
|
||||||
LOG_INFO("CSpot pause");
|
LOG_INFO("CSpot pause");
|
||||||
break;
|
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*);
|
uint32_t *remaining = va_arg(args, uint32_t*);
|
||||||
*remaining = (_buf_used(outputbuf) * 1000) / (output.current_sample_rate * BYTES_PER_FRAME);
|
*remaining = (_buf_used(outputbuf) * 1000) / (output.current_sample_rate * BYTES_PER_FRAME);
|
||||||
break;
|
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: {
|
case CSPOT_VOLUME: {
|
||||||
u32_t volume = va_arg(args, u32_t);
|
u32_t volume = va_arg(args, u32_t);
|
||||||
|
|||||||
Reference in New Issue
Block a user