new cspot/bell

This commit is contained in:
philippe44
2023-05-06 23:50:26 +02:00
parent e0e7e718ba
commit 8bad480112
163 changed files with 6611 additions and 6739 deletions

View File

@@ -6,13 +6,14 @@
#include <type_traits> // for remove_extent_t
#include <vector> // for vector
#include "BellLogger.h" // for AbstractLogger
#include "CSpotContext.h" // for Context
#include "Logger.h" // for CSPOT_LOG
#include "MercurySession.h" // for MercurySession, MercurySession::Res...
#include "Packet.h" // for cspot
#include "TimeProvider.h" // for TimeProvider
#include "Utils.h" // for string_format
#include "BellLogger.h" // for AbstractLogger
#include "CSpotContext.h" // for Context
#include "Logger.h" // for CSPOT_LOG
#include "MercurySession.h" // for MercurySession, MercurySession::Res...
#include "Packet.h" // for cspot
#include "TimeProvider.h" // for TimeProvider
#include "Utils.h" // for string_format
#include "WrappedSemaphore.h"
#ifdef BELL_ONLY_CJSON
#include "cJSON.h"
#else
@@ -22,11 +23,17 @@
using namespace cspot;
AccessKeyFetcher::AccessKeyFetcher(std::shared_ptr<cspot::Context> ctx) {
this->ctx = ctx;
}
static std::string CLIENT_ID =
"65b708073fc0480ea92a077233ca87bd"; // Spotify web client's client id
AccessKeyFetcher::~AccessKeyFetcher() {}
static std::string SCOPES =
"streaming,user-library-read,user-library-modify,user-top-read,user-read-"
"recently-played"; // Required access scopes
AccessKeyFetcher::AccessKeyFetcher(std::shared_ptr<cspot::Context> ctx)
: ctx(ctx) {
this->updateSemaphore = std::make_shared<bell::WrappedSemaphore>();
}
bool AccessKeyFetcher::isExpired() {
if (accessKey.empty()) {
@@ -40,11 +47,24 @@ bool AccessKeyFetcher::isExpired() {
return false;
}
void AccessKeyFetcher::getAccessKey(AccessKeyFetcher::Callback callback) {
std::string AccessKeyFetcher::getAccessKey() {
if (!isExpired()) {
return callback(accessKey);
return accessKey;
}
updateAccessKey();
return accessKey;
}
void AccessKeyFetcher::updateAccessKey() {
if (keyPending) {
// Already pending refresh request
return;
}
keyPending = true;
CSPOT_LOG(info, "Access token expired, fetching new one...");
std::string url =
@@ -54,17 +74,17 @@ void AccessKeyFetcher::getAccessKey(AccessKeyFetcher::Callback callback) {
ctx->session->execute(
MercurySession::RequestType::GET, url,
[this, timeProvider, callback](MercurySession::Response& res) {
[this, timeProvider](MercurySession::Response& res) {
if (res.fail)
return;
char* accessKeyJson = (char*)res.parts[0].data();
auto accessJSON = std::string(
accessKeyJson, strrchr(accessKeyJson, '}') - accessKeyJson + 1);
auto accessJSON =
std::string((char*)res.parts[0].data(), res.parts[0].size());
#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;
cJSON_Delete(jsonBody);
#else
auto jsonBody = nlohmann::json::parse(accessJSON);
this->accessKey = jsonBody["accessToken"];
@@ -74,11 +94,11 @@ void AccessKeyFetcher::getAccessKey(AccessKeyFetcher::Callback callback) {
this->expiresAt =
timeProvider->getSyncedTimestamp() + (expiresIn * 1000);
#ifdef BELL_ONLY_CJSON
callback(cJSON_GetObjectItem(jsonBody, "accessToken")->valuestring);
cJSON_Delete(jsonBody);
#else
callback(jsonBody["accessToken"]);
#endif
updateSemaphore->give();
});
updateSemaphore->twait(5000);
// Mark as not pending for refresh
keyPending = false;
}

View File

@@ -1,10 +1,10 @@
#include "ApResolve.h"
#include <initializer_list> // for initializer_list
#include <map> // for operator!=, operator==
#include <memory> // for allocator, unique_ptr
#include <string_view> // for string_view
#include <vector> // for vector
#include <initializer_list> // for initializer_list
#include <map> // for operator!=, operator==
#include <memory> // for allocator, unique_ptr
#include <string_view> // for string_view
#include <vector> // for vector
#include "HTTPClient.h" // for HTTPClient, HTTPClient::Response
#ifdef BELL_ONLY_CJSON
@@ -16,29 +16,27 @@
using namespace cspot;
ApResolve::ApResolve(std::string apOverride)
{
this->apOverride = apOverride;
ApResolve::ApResolve(std::string apOverride) {
this->apOverride = apOverride;
}
std::string ApResolve::fetchFirstApAddress()
{
if (apOverride != "")
{
return apOverride;
}
std::string ApResolve::fetchFirstApAddress() {
if (apOverride != "") {
return apOverride;
}
auto request = bell::HTTPClient::get("https://apresolve.spotify.com/");
std::string_view responseStr = request->body();
auto request = bell::HTTPClient::get("https://apresolve.spotify.com/");
std::string_view responseStr = request->body();
// parse json with nlohmann
// parse json with nlohmann
#ifdef 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
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
}

View File

@@ -1,8 +1,8 @@
#include "AuthChallenges.h"
#include <algorithm> // for copy
#include <climits> // for CHAR_BIT
#include <random> // for default_random_engine, independent_bits_en...
#include <algorithm> // for copy
#include <climits> // for CHAR_BIT
#include <random> // for default_random_engine, independent_bits_en...
#include "NanoPBHelper.h" // for pbPutString, pbEncode, pbDecode
#include "pb.h" // for pb_byte_t
@@ -94,9 +94,9 @@ std::vector<uint8_t> AuthChallenges::solveApHello(
// Get send and receive keys
this->shanSendKey = std::vector<uint8_t>(resultData.begin() + 0x14,
resultData.begin() + 0x34);
resultData.begin() + 0x34);
this->shanRecvKey = std::vector<uint8_t>(resultData.begin() + 0x34,
resultData.begin() + 0x54);
resultData.begin() + 0x54);
return pbEncode(ClientResponsePlaintext_fields, &clientResPlaintext);
}
@@ -125,5 +125,6 @@ std::vector<uint8_t> AuthChallenges::prepareClientHello() {
// Generate the random nonce
auto nonce = crypto->generateVectorWithRandomData(16);
std::copy(nonce.begin(), nonce.end(), clientHello.client_nonce);
return pbEncode(ClientHello_fields, &clientHello);
}
}

View File

@@ -1,18 +1,18 @@
#include "LoginBlob.h"
#include <stdio.h> // for sprintf
#include <initializer_list> // for initializer_list
#include <stdio.h> // for sprintf
#include <initializer_list> // for initializer_list
#include "BellLogger.h" // for AbstractLogger
#include "ConstantParameters.h" // for brandName, cspot, protoc...
#include "Logger.h" // for CSPOT_LOG
#include "protobuf/authentication.pb.h" // for AuthenticationType_AUTHE...
#ifdef BELL_ONLY_CJSON
#include "cJSON.h"
#include "cJSON.h "
#else
#include "nlohmann/detail/json_pointer.hpp" // for json_pointer<>::string_t
#include "nlohmann/json.hpp" // for basic_json<>::object_t
#include "nlohmann/json_fwd.hpp" // for json
#include "nlohmann/json.hpp" // for basic_json<>::object_t, basic_json
#include "nlohmann/json_fwd.hpp" // for json
#endif
using namespace cspot;
@@ -24,7 +24,7 @@ LoginBlob::LoginBlob(std::string name) {
this->deviceId = std::string("142137fd329622137a149016") + std::string(hash);
this->crypto = std::make_unique<Crypto>();
this->name = name;
this->crypto->dhInit();
}
@@ -142,7 +142,8 @@ void LoginBlob::loadJson(const std::string& json) {
cJSON* root = cJSON_Parse(json.c_str());
this->authType = cJSON_GetObjectItem(root, "authType")->valueint;
this->username = cJSON_GetObjectItem(root, "username")->valuestring;
std::string authDataObject = cJSON_GetObjectItem(root, "authData")->valuestring;
std::string authDataObject =
cJSON_GetObjectItem(root, "authData")->valuestring;
cJSON_Delete(root);
#else
auto root = nlohmann::json::parse(json);
@@ -151,23 +152,24 @@ void LoginBlob::loadJson(const std::string& json) {
std::string authDataObject = root["authData"];
this->authData = crypto->base64Decode(authDataObject);
#endif
#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* 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);
char* str = cJSON_PrintUnformatted(json_obj);
cJSON_Delete(json_obj);
std::string json_objStr(str);
free(str);
return json_objStr;
#else
#else
nlohmann::json obj;
obj["authData"] = crypto->base64Encode(authData);
obj["authType"] = this->authType;
@@ -200,7 +202,7 @@ std::string LoginBlob::buildZeroconfInfo() {
auto encodedKey = crypto->base64Encode(crypto->publicKey);
#ifdef BELL_ONLY_CJSON
cJSON* json_obj = cJSON_CreateObject();
cJSON* json_obj = cJSON_CreateObject();
cJSON_AddNumberToObject(json_obj, "status", 101);
cJSON_AddStringToObject(json_obj, "statusString", "OK");
cJSON_AddStringToObject(json_obj, "version", cspot::protocolVersion);
@@ -214,20 +216,21 @@ std::string LoginBlob::buildZeroconfInfo() {
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);
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
#else
nlohmann::json obj;
obj["status"] = 101;
obj["statusString"] = "OK";

View File

@@ -6,11 +6,9 @@
#include <stdexcept> // for runtime_error
#include <type_traits> // for remove_extent_t, __underlying_type_impl<>:...
#include <utility> // for pair
#ifndef _WIN32
#include <arpa/inet.h>
#include <arpa/inet.h> // for htons, ntohs, htonl, ntohl
#endif
#include "BellLogger.h" // for AbstractLogger
#include "BellTask.h" // for Task
#include "BellUtils.h" // for BELL_SLEEP_MS
@@ -110,6 +108,22 @@ bool MercurySession::triggerTimeout() {
return false;
}
void MercurySession::unregister(uint64_t sequenceId) {
auto callback = this->callbacks.find(sequenceId);
if (callback != this->callbacks.end()) {
this->callbacks.erase(callback);
}
}
void MercurySession::unregisterAudioKey(uint32_t sequenceId) {
auto callback = this->audioKeyCallbacks.find(sequenceId);
if (callback != this->audioKeyCallbacks.end()) {
this->audioKeyCallbacks.erase(callback);
}
}
void MercurySession::disconnect() {
CSPOT_LOG(info, "Disconnecting mercury session");
this->isRunning = false;
@@ -145,12 +159,13 @@ void MercurySession::handlePacket() {
// First four bytes mark the sequence id
auto seqId = ntohl(extract<uint32_t>(packet.data, 0));
if (seqId == (this->audioKeySequence - 1) &&
audioKeyCallback != nullptr) {
if (this->audioKeyCallbacks.count(seqId) > 0) {
auto success = static_cast<RequestType>(packet.command) ==
RequestType::AUDIO_KEY_SUCCESS_RESPONSE;
audioKeyCallback(success, packet.data);
this->audioKeyCallbacks[seqId](success, packet.data);
}
break;
}
case RequestType::SEND:
@@ -290,23 +305,30 @@ uint64_t MercurySession::executeSubscription(RequestType method,
// Bump sequence id
this->sequenceId += 1;
this->shanConn->sendPacket(
static_cast<std::underlying_type<RequestType>::type>(method),
sequenceIdBytes);
try {
this->shanConn->sendPacket(
static_cast<std::underlying_type<RequestType>::type>(method),
sequenceIdBytes);
} catch (...) {
// @TODO: handle disconnect
}
return this->sequenceId - 1;
}
void MercurySession::requestAudioKey(const std::vector<uint8_t>& trackId,
const std::vector<uint8_t>& fileId,
AudioKeyCallback audioCallback) {
uint32_t MercurySession::requestAudioKey(const std::vector<uint8_t>& trackId,
const std::vector<uint8_t>& fileId,
AudioKeyCallback audioCallback) {
auto buffer = fileId;
this->audioKeyCallback = audioCallback;
// Store callback
this->audioKeyCallbacks.insert({this->audioKeySequence, audioCallback});
// Structure: [FILEID] [TRACKID] [4 BYTES SEQUENCE ID] [0x00, 0x00]
buffer.insert(buffer.end(), trackId.begin(), trackId.end());
auto audioKeySequence = pack<uint32_t>(htonl(this->audioKeySequence));
buffer.insert(buffer.end(), audioKeySequence.begin(), audioKeySequence.end());
auto audioKeySequenceBuffer = pack<uint32_t>(htonl(this->audioKeySequence));
buffer.insert(buffer.end(), audioKeySequenceBuffer.begin(),
audioKeySequenceBuffer.end());
auto suffix = std::vector<uint8_t>({0x00, 0x00});
buffer.insert(buffer.end(), suffix.begin(), suffix.end());
@@ -315,6 +337,11 @@ void MercurySession::requestAudioKey(const std::vector<uint8_t>& trackId,
// Used for broken connection detection
// this->lastRequestTimestamp = timeProvider->getSyncedTimestamp();
this->shanConn->sendPacket(
static_cast<uint8_t>(RequestType::AUDIO_KEY_REQUEST_COMMAND), buffer);
try {
this->shanConn->sendPacket(
static_cast<uint8_t>(RequestType::AUDIO_KEY_REQUEST_COMMAND), buffer);
} catch (...) {
// @TODO: Handle disconnect
}
return audioKeySequence - 1;
}

View File

@@ -1,19 +1,17 @@
#include "PlainConnection.h"
#ifndef _WIN32
#include <netdb.h> // for addrinfo, freeaddrinfo, getaddrinfo
#include <netinet/in.h> // for IPPROTO_IP, IPPROTO_TCP
#include <sys/errno.h> // for EAGAIN, EINTR, ETIMEDOUT, errno
#include <sys/socket.h> // for setsockopt, connect, recv, send, shutdown
#include <sys/time.h> // for timeval
#endif
#include <cstring> // for memset
#include <stdexcept> // for runtime_error
#ifdef _WIN32
#include <ws2tcpip.h>
#else
#include <netdb.h> // for addrinfo, freeaddrinfo, getaddrinfo
#include <netinet/in.h> // for IPPROTO_IP, IPPROTO_TCP
#include <sys/errno.h> // for EAGAIN, EINTR, ETIMEDOUT, errno
#include <sys/socket.h> // for setsockopt, connect, recv, send, shutdown
#include <sys/time.h> // for timeval
#include <cstring> // for memset
#include <stdexcept> // for runtime_error
#include <netinet/tcp.h> // for TCP_NODELAY
#include <arpa/inet.h>
#include <netdb.h>
#else
#include <ws2tcpip.h>
#endif
#include "BellLogger.h" // for AbstractLogger
#include "Logger.h" // for CSPOT_LOG
@@ -67,8 +65,8 @@ void PlainConnection::connect(const std::string& apAddress) {
if (this->apSock < 0)
continue;
if (::connect(this->apSock, (struct sockaddr*)ai->ai_addr, ai->ai_addrlen) !=
-1) {
if (::connect(this->apSock, (struct sockaddr*)ai->ai_addr,
ai->ai_addrlen) != -1) {
#ifdef _WIN32
uint32_t tv = 3000;
#else
@@ -144,10 +142,10 @@ void PlainConnection::readBlock(const uint8_t* dst, size_t size) {
switch (getErrno()) {
case EAGAIN:
case ETIMEDOUT:
// if (timeoutHandler()) {
// CSPOT_LOG(error, "Connection lost, will need to reconnect...");
// throw std::runtime_error("Reconnection required");
// }
if (timeoutHandler()) {
CSPOT_LOG(error, "Connection lost, will need to reconnect...");
throw std::runtime_error("Reconnection required");
}
goto READ;
case EINTR:
break;
@@ -174,9 +172,9 @@ size_t PlainConnection::writeBlock(const std::vector<uint8_t>& data) {
switch (getErrno()) {
case EAGAIN:
case ETIMEDOUT:
// if (timeoutHandler()) {
// throw std::runtime_error("Reconnection required");
// }
if (timeoutHandler()) {
throw std::runtime_error("Reconnection required");
}
goto WRITE;
case EINTR:
break;

View File

@@ -1,11 +1,12 @@
#include "PlaybackState.h"
#include <string.h> // for strdup, memcpy, strcpy, strlen
#include <cstdint> // for uint8_t
#include <cstdlib> // for free, NULL, realloc, rand
#include <memory> // for shared_ptr
#include <type_traits> // for remove_extent_t
#include <utility> // for swap
#include <string.h> // for strdup, memcpy, strcpy, strlen
#include <cstdint> // for uint8_t
#include <cstdlib> // for free, NULL, realloc, rand
#include <cstring>
#include <memory> // for shared_ptr
#include <type_traits> // for remove_extent_t
#include <utility> // for swap
#include "BellLogger.h" // for AbstractLogger
#include "CSpotContext.h" // for Context::ConfigState, Context (ptr o...
@@ -15,6 +16,7 @@
#include "Packet.h" // for cspot
#include "pb.h" // for pb_bytes_array_t, PB_BYTES_ARRAY_T_A...
#include "pb_decode.h" // for pb_release
#include "protobuf/spirc.pb.h"
using namespace cspot;
@@ -23,6 +25,13 @@ PlaybackState::PlaybackState(std::shared_ptr<cspot::Context> ctx) {
innerFrame = {};
remoteFrame = {};
// Prepare callbacks for decoding of remote frame track data
remoteFrame.state.track.funcs.decode = &TrackReference::pbDecodeTrackList;
remoteFrame.state.track.arg = &remoteTracks;
innerFrame.ident = strdup(ctx->config.deviceId.c_str());
innerFrame.protocol_version = strdup(protocolVersion);
// Prepare default state
innerFrame.state.has_position_ms = true;
innerFrame.state.position_ms = 0;
@@ -52,13 +61,12 @@ PlaybackState::PlaybackState(std::shared_ptr<cspot::Context> ctx) {
innerFrame.device_state.name = strdup(ctx->config.deviceName.c_str());
innerFrame.state.track_count = 0;
// Prepare player's capabilities
addCapability(CapabilityType_kCanBePlayer, 1);
addCapability(CapabilityType_kDeviceType, 4);
addCapability(CapabilityType_kGaiaEqConnectId, 1);
addCapability(CapabilityType_kSupportsLogout, 0);
addCapability(CapabilityType_kSupportsPlaylistV2, 1);
addCapability(CapabilityType_kIsObservable, 1);
addCapability(CapabilityType_kVolumeSteps, 64);
addCapability(CapabilityType_kSupportedContexts, -1,
@@ -66,8 +74,8 @@ PlaybackState::PlaybackState(std::shared_ptr<cspot::Context> ctx) {
"inbox", "toplist", "starred",
"publishedstarred", "track"}));
addCapability(CapabilityType_kSupportedTypes, -1,
std::vector<std::string>({"audio/local", "audio/track",
"audio/episode", "local", "track"}));
std::vector<std::string>(
{"audio/track", "audio/episode", "audio/episode+track"}));
innerFrame.device_state.capabilities_count = 8;
}
@@ -102,34 +110,20 @@ void PlaybackState::setPlaybackState(const PlaybackState::State state) {
}
}
void PlaybackState::syncWithRemote() {
innerFrame.state.context_uri = (char*)realloc(
innerFrame.state.context_uri, strlen(remoteFrame.state.context_uri) + 1);
strcpy(innerFrame.state.context_uri, remoteFrame.state.context_uri);
innerFrame.state.has_playing_track_index = true;
innerFrame.state.playing_track_index = remoteFrame.state.playing_track_index;
}
bool PlaybackState::isActive() {
return innerFrame.device_state.is_active;
}
bool PlaybackState::nextTrack() {
innerFrame.state.playing_track_index++;
if (innerFrame.state.playing_track_index >= innerFrame.state.track_count) {
innerFrame.state.playing_track_index = 0;
if (!innerFrame.state.repeat) {
setPlaybackState(State::Paused);
return false;
}
}
return true;
}
void PlaybackState::prevTrack() {
if (innerFrame.state.playing_track_index > 0) {
innerFrame.state.playing_track_index--;
} else if (innerFrame.state.repeat) {
innerFrame.state.playing_track_index = innerFrame.state.track_count - 1;
}
}
void PlaybackState::setActive(bool isActive) {
innerFrame.device_state.is_active = isActive;
if (isActive) {
@@ -145,131 +139,25 @@ void PlaybackState::updatePositionMs(uint32_t position) {
ctx->timeProvider->getSyncedTimestamp();
}
#define FREE(ptr) \
{ \
free(ptr); \
ptr = NULL; \
}
#define STRDUP(dst, src) \
if (src != NULL) { \
dst = strdup(src); \
} else { \
FREE(dst); \
} // strdup null pointer safe
void PlaybackState::updateTracks() {
CSPOT_LOG(info, "---- Track count %d", remoteFrame.state.track_count);
CSPOT_LOG(info, "---- Inner track count %d", innerFrame.state.track_count);
CSPOT_LOG(info, "--- Context URI %s", remoteFrame.state.context_uri);
// free unused tracks
if (innerFrame.state.track_count > remoteFrame.state.track_count) {
for (uint16_t i = remoteFrame.state.track_count;
i < innerFrame.state.track_count; ++i) {
FREE(innerFrame.state.track[i].gid);
FREE(innerFrame.state.track[i].uri);
FREE(innerFrame.state.track[i].context);
}
}
// reallocate memory for new tracks
innerFrame.state.track = (TrackRef*)realloc(
innerFrame.state.track, sizeof(TrackRef) * remoteFrame.state.track_count);
for (uint16_t i = 0; i < remoteFrame.state.track_count; ++i) {
if (i >= innerFrame.state.track_count) {
innerFrame.state.track[i].gid = NULL;
innerFrame.state.track[i].uri = NULL;
innerFrame.state.track[i].context = NULL;
}
if (remoteFrame.state.track[i].gid != NULL) {
uint16_t gid_size = remoteFrame.state.track[i].gid->size;
innerFrame.state.track[i].gid = (pb_bytes_array_t*)realloc(
innerFrame.state.track[i].gid, PB_BYTES_ARRAY_T_ALLOCSIZE(gid_size));
memcpy(innerFrame.state.track[i].gid->bytes,
remoteFrame.state.track[i].gid->bytes, gid_size);
innerFrame.state.track[i].gid->size = gid_size;
}
innerFrame.state.track[i].has_queued =
remoteFrame.state.track[i].has_queued;
innerFrame.state.track[i].queued = remoteFrame.state.track[i].queued;
STRDUP(innerFrame.state.track[i].uri, remoteFrame.state.track[i].uri);
STRDUP(innerFrame.state.track[i].context,
remoteFrame.state.track[i].context);
}
innerFrame.state.context_uri = (char*)realloc(
innerFrame.state.context_uri, strlen(remoteFrame.state.context_uri) + 1);
strcpy(innerFrame.state.context_uri, remoteFrame.state.context_uri);
innerFrame.state.track_count = remoteFrame.state.track_count;
innerFrame.state.has_playing_track_index = true;
innerFrame.state.playing_track_index = remoteFrame.state.playing_track_index;
if (remoteFrame.state.repeat) {
setRepeat(true);
}
if (remoteFrame.state.shuffle) {
setShuffle(true);
}
}
void PlaybackState::setVolume(uint32_t volume) {
innerFrame.device_state.volume = volume;
ctx->config.volume = volume;
}
void PlaybackState::setShuffle(bool shuffle) {
innerFrame.state.shuffle = shuffle;
if (shuffle) {
// Put current song at the begining
std::swap(innerFrame.state.track[0],
innerFrame.state.track[innerFrame.state.playing_track_index]);
bool PlaybackState::decodeRemoteFrame(std::vector<uint8_t>& data) {
pb_release(Frame_fields, &remoteFrame);
// Shuffle current tracks
for (int x = 1; x < innerFrame.state.track_count - 1; x++) {
auto j = x + (std::rand() % (innerFrame.state.track_count - x));
std::swap(innerFrame.state.track[j], innerFrame.state.track[x]);
}
innerFrame.state.playing_track_index = 0;
}
}
remoteTracks.clear();
void PlaybackState::setRepeat(bool repeat) {
innerFrame.state.repeat = repeat;
}
pbDecode(remoteFrame, Frame_fields, data);
TrackRef* PlaybackState::getCurrentTrackRef() {
if (innerFrame.state.playing_track_index >= innerFrame.state.track_count) {
return nullptr;
}
return &innerFrame.state.track[innerFrame.state.playing_track_index];
}
TrackRef* PlaybackState::getNextTrackRef() {
if ((innerFrame.state.playing_track_index + 1) >= innerFrame.state.track_count) {
if (innerFrame.state.repeat) {
return &innerFrame.state.track[0];
}
return nullptr;
}
return &innerFrame.state.track[innerFrame.state.playing_track_index + 1];
return true;
}
std::vector<uint8_t> PlaybackState::encodeCurrentFrame(MessageType typ) {
free(innerFrame.ident);
free(innerFrame.protocol_version);
// Prepare current frame info
innerFrame.version = 1;
innerFrame.ident = strdup(ctx->config.deviceId.c_str());
innerFrame.seq_nr = this->seqNum;
innerFrame.protocol_version = strdup(protocolVersion);
innerFrame.typ = typ;
innerFrame.state_update_id = ctx->timeProvider->getSyncedTimestamp();
innerFrame.has_version = true;
@@ -281,6 +169,7 @@ std::vector<uint8_t> PlaybackState::encodeCurrentFrame(MessageType typ) {
innerFrame.has_state_update_id = true;
this->seqNum += 1;
return pbEncode(Frame_fields, &innerFrame);
}
@@ -308,5 +197,6 @@ void PlaybackState::addCapability(CapabilityType typ, int intValue,
this->innerFrame.device_state.capabilities[capabilityIndex]
.stringValue_count = stringValue.size();
this->capabilityIndex += 1;
}

View File

@@ -5,437 +5,389 @@
using std::size_t;
static inline uint32_t rotl(uint32_t n, unsigned int c)
{
const unsigned int mask = (CHAR_BIT * sizeof(n) - 1); // assumes width is a power of 2.
// assert ( (c<=mask) &&"rotate by type width or more");
c &= mask;
return (n << c) | (n >> ((-c) & mask));
static inline uint32_t rotl(uint32_t n, unsigned int c) {
const unsigned int mask =
(CHAR_BIT * sizeof(n) - 1); // assumes width is a power of 2.
// assert ( (c<=mask) &&"rotate by type width or more");
c &= mask;
return (n << c) | (n >> ((-c) & mask));
}
static inline uint32_t rotr(uint32_t n, unsigned int c)
{
const unsigned int mask = (CHAR_BIT * sizeof(n) - 1); // assumes width is a power of 2.
// assert ( (c<=mask) &&"rotate by type width or more");
c &= mask;
return (n >> c) | (n << ((-c) & mask));
static inline uint32_t rotr(uint32_t n, unsigned int c) {
const unsigned int mask =
(CHAR_BIT * sizeof(n) - 1); // assumes width is a power of 2.
// assert ( (c<=mask) &&"rotate by type width or more");
c &= mask;
return (n >> c) | (n << ((-c) & mask));
}
uint32_t Shannon::sbox1(uint32_t w)
{
w ^= rotl(w, 5) | rotl(w, 7);
w ^= rotl(w, 19) | rotl(w, 22);
return w;
uint32_t Shannon::sbox1(uint32_t w) {
w ^= rotl(w, 5) | rotl(w, 7);
w ^= rotl(w, 19) | rotl(w, 22);
return w;
}
uint32_t Shannon::sbox2(uint32_t w)
{
w ^= rotl(w, 7) | rotl(w, 22);
w ^= rotl(w, 5) | rotl(w, 19);
return w;
uint32_t Shannon::sbox2(uint32_t w) {
w ^= rotl(w, 7) | rotl(w, 22);
w ^= rotl(w, 5) | rotl(w, 19);
return w;
}
void Shannon::cycle()
{
uint32_t t;
int i;
void Shannon::cycle() {
uint32_t t;
int i;
/* nonlinear feedback function */
t = this->R[12] ^ this->R[13] ^ this->konst;
t = Shannon::sbox1(t) ^ rotl(this->R[0], 1);
/* shift register */
for (i = 1; i < N; ++i)
this->R[i - 1] = this->R[i];
this->R[N - 1] = t;
t = Shannon::sbox2(this->R[2] ^ this->R[15]);
this->R[0] ^= t;
this->sbuf = t ^ this->R[8] ^ this->R[12];
/* nonlinear feedback function */
t = this->R[12] ^ this->R[13] ^ this->konst;
t = Shannon::sbox1(t) ^ rotl(this->R[0], 1);
/* shift register */
for (i = 1; i < N; ++i)
this->R[i - 1] = this->R[i];
this->R[N - 1] = t;
t = Shannon::sbox2(this->R[2] ^ this->R[15]);
this->R[0] ^= t;
this->sbuf = t ^ this->R[8] ^ this->R[12];
}
void Shannon::crcfunc(uint32_t i)
{
uint32_t t;
int j;
void Shannon::crcfunc(uint32_t i) {
uint32_t t;
int j;
/* Accumulate CRC of input */
t = this->CRC[0] ^ this->CRC[2] ^ this->CRC[15] ^ i;
for (j = 1; j < N; ++j)
this->CRC[j - 1] = this->CRC[j];
this->CRC[N - 1] = t;
/* Accumulate CRC of input */
t = this->CRC[0] ^ this->CRC[2] ^ this->CRC[15] ^ i;
for (j = 1; j < N; ++j)
this->CRC[j - 1] = this->CRC[j];
this->CRC[N - 1] = t;
}
void Shannon::macfunc(uint32_t i)
{
this->crcfunc(i);
this->R[KEYP] ^= i;
void Shannon::macfunc(uint32_t i) {
this->crcfunc(i);
this->R[KEYP] ^= i;
}
void Shannon::initState()
{
int i;
void Shannon::initState() {
int i;
/* Register initialised to Fibonacci numbers; Counter zeroed. */
this->R[0] = 1;
this->R[1] = 1;
for (i = 2; i < N; ++i)
this->R[i] = this->R[i - 1] + this->R[i - 2];
this->konst = Shannon::INITKONST;
/* Register initialised to Fibonacci numbers; Counter zeroed. */
this->R[0] = 1;
this->R[1] = 1;
for (i = 2; i < N; ++i)
this->R[i] = this->R[i - 1] + this->R[i - 2];
this->konst = Shannon::INITKONST;
}
void Shannon::saveState()
{
int i;
for (i = 0; i < Shannon::N; ++i)
this->initR[i] = this->R[i];
void Shannon::saveState() {
int i;
for (i = 0; i < Shannon::N; ++i)
this->initR[i] = this->R[i];
}
void Shannon::reloadState()
{
int i;
void Shannon::reloadState() {
int i;
for (i = 0; i < Shannon::N; ++i)
this->R[i] = this->initR[i];
for (i = 0; i < Shannon::N; ++i)
this->R[i] = this->initR[i];
}
void Shannon::genkonst()
{
this->konst = this->R[0];
void Shannon::genkonst() {
this->konst = this->R[0];
}
void Shannon::diffuse()
{
int i;
void Shannon::diffuse() {
int i;
for (i = 0; i < Shannon::FOLD; ++i)
this->cycle();
for (i = 0; i < Shannon::FOLD; ++i)
this->cycle();
}
#define Byte(x, i) ((uint32_t)(((x) >> (8 * (i))) & 0xFF))
#define BYTE2WORD(b) ( \
(((uint32_t)(b)[3] & 0xFF) << 24) | \
(((uint32_t)(b)[2] & 0xFF) << 16) | \
(((uint32_t)(b)[1] & 0xFF) << 8) | \
(((uint32_t)(b)[0] & 0xFF)))
#define WORD2BYTE(w, b) \
{ \
(b)[3] = Byte(w, 3); \
(b)[2] = Byte(w, 2); \
(b)[1] = Byte(w, 1); \
(b)[0] = Byte(w, 0); \
}
#define XORWORD(w, b) \
{ \
(b)[3] ^= Byte(w, 3); \
(b)[2] ^= Byte(w, 2); \
(b)[1] ^= Byte(w, 1); \
(b)[0] ^= Byte(w, 0); \
}
#define BYTE2WORD(b) \
((((uint32_t)(b)[3] & 0xFF) << 24) | (((uint32_t)(b)[2] & 0xFF) << 16) | \
(((uint32_t)(b)[1] & 0xFF) << 8) | (((uint32_t)(b)[0] & 0xFF)))
#define WORD2BYTE(w, b) \
{ \
(b)[3] = Byte(w, 3); \
(b)[2] = Byte(w, 2); \
(b)[1] = Byte(w, 1); \
(b)[0] = Byte(w, 0); \
}
#define XORWORD(w, b) \
{ \
(b)[3] ^= Byte(w, 3); \
(b)[2] ^= Byte(w, 2); \
(b)[1] ^= Byte(w, 1); \
(b)[0] ^= Byte(w, 0); \
}
#define XORWORD(w, b) \
{ \
(b)[3] ^= Byte(w, 3); \
(b)[2] ^= Byte(w, 2); \
(b)[1] ^= Byte(w, 1); \
(b)[0] ^= Byte(w, 0); \
}
#define XORWORD(w, b) \
{ \
(b)[3] ^= Byte(w, 3); \
(b)[2] ^= Byte(w, 2); \
(b)[1] ^= Byte(w, 1); \
(b)[0] ^= Byte(w, 0); \
}
/* Load key material into the register
*/
#define ADDKEY(k) \
this->R[KEYP] ^= (k);
#define ADDKEY(k) this->R[KEYP] ^= (k);
void Shannon::loadKey(const std::vector<uint8_t>& key)
{
int i, j;
uint32_t k;
uint8_t xtra[4];
size_t keylen = key.size();
/* start folding in key */
for (i = 0; i < (keylen & ~0x3); i += 4)
{
k = BYTE2WORD(&key[i]);
ADDKEY(k);
this->cycle();
}
/* if there were any extra key bytes, zero pad to a word */
if (i < keylen)
{
for (j = 0 /* i unchanged */; i < keylen; ++i)
xtra[j++] = key[i];
for (/* j unchanged */; j < 4; ++j)
xtra[j] = 0;
k = BYTE2WORD(xtra);
ADDKEY(k);
this->cycle();
}
/* also fold in the length of the key */
ADDKEY(keylen);
void Shannon::loadKey(const std::vector<uint8_t>& key) {
int i, j;
uint32_t k;
uint8_t xtra[4];
size_t keylen = key.size();
/* start folding in key */
for (i = 0; i < (keylen & ~0x3); i += 4) {
k = BYTE2WORD(&key[i]);
ADDKEY(k);
this->cycle();
}
/* save a copy of the register */
for (i = 0; i < N; ++i)
this->CRC[i] = this->R[i];
/* if there were any extra key bytes, zero pad to a word */
if (i < keylen) {
for (j = 0 /* i unchanged */; i < keylen; ++i)
xtra[j++] = key[i];
for (/* j unchanged */; j < 4; ++j)
xtra[j] = 0;
k = BYTE2WORD(xtra);
ADDKEY(k);
this->cycle();
}
/* now diffuse */
this->diffuse();
/* also fold in the length of the key */
ADDKEY(keylen);
this->cycle();
/* now xor the copy back -- makes key loading irreversible */
for (i = 0; i < N; ++i)
this->R[i] ^= this->CRC[i];
/* save a copy of the register */
for (i = 0; i < N; ++i)
this->CRC[i] = this->R[i];
/* now diffuse */
this->diffuse();
/* now xor the copy back -- makes key loading irreversible */
for (i = 0; i < N; ++i)
this->R[i] ^= this->CRC[i];
}
void Shannon::key(const std::vector<uint8_t>& key)
{
this->initState();
this->loadKey(key);
this->genkonst(); /* in case we proceed to stream generation */
this->saveState();
this->nbuf = 0;
void Shannon::key(const std::vector<uint8_t>& key) {
this->initState();
this->loadKey(key);
this->genkonst(); /* in case we proceed to stream generation */
this->saveState();
this->nbuf = 0;
}
void Shannon::nonce(const std::vector<uint8_t>& nonce)
{
this->reloadState();
this->konst = Shannon::INITKONST;
this->loadKey(nonce);
this->genkonst();
this->nbuf = 0;
void Shannon::nonce(const std::vector<uint8_t>& nonce) {
this->reloadState();
this->konst = Shannon::INITKONST;
this->loadKey(nonce);
this->genkonst();
this->nbuf = 0;
}
void Shannon::stream(std::vector<uint8_t>& bufVec)
{
uint8_t* endbuf;
size_t nbytes = bufVec.size();
uint8_t* buf = bufVec.data();
/* handle any previously buffered bytes */
while (this->nbuf != 0 && nbytes != 0)
{
*buf++ ^= this->sbuf & 0xFF;
this->sbuf >>= 8;
this->nbuf -= 8;
--nbytes;
}
void Shannon::stream(std::vector<uint8_t>& bufVec) {
uint8_t* endbuf;
size_t nbytes = bufVec.size();
uint8_t* buf = bufVec.data();
/* handle any previously buffered bytes */
while (this->nbuf != 0 && nbytes != 0) {
*buf++ ^= this->sbuf & 0xFF;
this->sbuf >>= 8;
this->nbuf -= 8;
--nbytes;
}
/* handle whole words */
endbuf = &buf[nbytes & ~((uint32_t)0x03)];
while (buf < endbuf)
{
this->cycle();
XORWORD(this->sbuf, buf);
buf += 4;
}
/* handle whole words */
endbuf = &buf[nbytes & ~((uint32_t)0x03)];
while (buf < endbuf) {
this->cycle();
XORWORD(this->sbuf, buf);
buf += 4;
}
/* handle any trailing bytes */
nbytes &= 0x03;
if (nbytes != 0)
{
this->cycle();
this->nbuf = 32;
while (this->nbuf != 0 && nbytes != 0)
{
*buf++ ^= this->sbuf & 0xFF;
this->sbuf >>= 8;
this->nbuf -= 8;
--nbytes;
}
/* handle any trailing bytes */
nbytes &= 0x03;
if (nbytes != 0) {
this->cycle();
this->nbuf = 32;
while (this->nbuf != 0 && nbytes != 0) {
*buf++ ^= this->sbuf & 0xFF;
this->sbuf >>= 8;
this->nbuf -= 8;
--nbytes;
}
}
}
void Shannon::maconly(std::vector<uint8_t>& bufVec)
{
size_t nbytes = bufVec.size();
uint8_t* buf = bufVec.data();
void Shannon::maconly(std::vector<uint8_t>& bufVec) {
size_t nbytes = bufVec.size();
uint8_t* buf = bufVec.data();
uint8_t* endbuf;
uint8_t* endbuf;
/* handle any previously buffered bytes */
if (this->nbuf != 0)
{
while (this->nbuf != 0 && nbytes != 0)
{
this->mbuf ^= (*buf++) << (32 - this->nbuf);
this->nbuf -= 8;
--nbytes;
}
if (this->nbuf != 0) /* not a whole word yet */
return;
/* LFSR already cycled */
this->macfunc(this->mbuf);
/* handle any previously buffered bytes */
if (this->nbuf != 0) {
while (this->nbuf != 0 && nbytes != 0) {
this->mbuf ^= (*buf++) << (32 - this->nbuf);
this->nbuf -= 8;
--nbytes;
}
if (this->nbuf != 0) /* not a whole word yet */
return;
/* LFSR already cycled */
this->macfunc(this->mbuf);
}
/* handle whole words */
endbuf = &buf[nbytes & ~((uint32_t)0x03)];
while (buf < endbuf)
{
this->cycle();
this->macfunc(BYTE2WORD(buf));
buf += 4;
}
/* handle whole words */
endbuf = &buf[nbytes & ~((uint32_t)0x03)];
while (buf < endbuf) {
this->cycle();
this->macfunc(BYTE2WORD(buf));
buf += 4;
}
/* handle any trailing bytes */
nbytes &= 0x03;
if (nbytes != 0)
{
this->cycle();
this->mbuf = 0;
this->nbuf = 32;
while (this->nbuf != 0 && nbytes != 0)
{
this->mbuf ^= (*buf++) << (32 - this->nbuf);
this->nbuf -= 8;
--nbytes;
}
/* handle any trailing bytes */
nbytes &= 0x03;
if (nbytes != 0) {
this->cycle();
this->mbuf = 0;
this->nbuf = 32;
while (this->nbuf != 0 && nbytes != 0) {
this->mbuf ^= (*buf++) << (32 - this->nbuf);
this->nbuf -= 8;
--nbytes;
}
}
}
void Shannon::encrypt(std::vector<uint8_t>& bufVec)
{
size_t nbytes = bufVec.size();
uint8_t* buf = bufVec.data();
uint8_t* endbuf;
uint32_t t = 0;
void Shannon::encrypt(std::vector<uint8_t>& bufVec) {
size_t nbytes = bufVec.size();
uint8_t* buf = bufVec.data();
uint8_t* endbuf;
uint32_t t = 0;
/* handle any previously buffered bytes */
if (this->nbuf != 0)
{
while (this->nbuf != 0 && nbytes != 0)
{
this->mbuf ^= *buf << (32 - this->nbuf);
*buf ^= (this->sbuf >> (32 - this->nbuf)) & 0xFF;
++buf;
this->nbuf -= 8;
--nbytes;
}
if (this->nbuf != 0) /* not a whole word yet */
return;
/* LFSR already cycled */
this->macfunc(this->mbuf);
/* handle any previously buffered bytes */
if (this->nbuf != 0) {
while (this->nbuf != 0 && nbytes != 0) {
this->mbuf ^= *buf << (32 - this->nbuf);
*buf ^= (this->sbuf >> (32 - this->nbuf)) & 0xFF;
++buf;
this->nbuf -= 8;
--nbytes;
}
if (this->nbuf != 0) /* not a whole word yet */
return;
/* LFSR already cycled */
this->macfunc(this->mbuf);
}
/* handle whole words */
endbuf = &buf[nbytes & ~((uint32_t)0x03)];
while (buf < endbuf)
{
this->cycle();
t = BYTE2WORD(buf);
this->macfunc(t);
t ^= this->sbuf;
WORD2BYTE(t, buf);
buf += 4;
}
/* handle whole words */
endbuf = &buf[nbytes & ~((uint32_t)0x03)];
while (buf < endbuf) {
this->cycle();
t = BYTE2WORD(buf);
this->macfunc(t);
t ^= this->sbuf;
WORD2BYTE(t, buf);
buf += 4;
}
/* handle any trailing bytes */
nbytes &= 0x03;
if (nbytes != 0)
{
this->cycle();
this->mbuf = 0;
this->nbuf = 32;
while (this->nbuf != 0 && nbytes != 0)
{
this->mbuf ^= *buf << (32 - this->nbuf);
*buf ^= (this->sbuf >> (32 - this->nbuf)) & 0xFF;
++buf;
this->nbuf -= 8;
--nbytes;
}
/* handle any trailing bytes */
nbytes &= 0x03;
if (nbytes != 0) {
this->cycle();
this->mbuf = 0;
this->nbuf = 32;
while (this->nbuf != 0 && nbytes != 0) {
this->mbuf ^= *buf << (32 - this->nbuf);
*buf ^= (this->sbuf >> (32 - this->nbuf)) & 0xFF;
++buf;
this->nbuf -= 8;
--nbytes;
}
}
}
void Shannon::decrypt(std::vector<uint8_t>& bufVec) {
size_t nbytes = bufVec.size();
uint8_t* buf = bufVec.data();
uint8_t* endbuf;
uint32_t t = 0;
void Shannon::decrypt(std::vector<uint8_t>& bufVec)
{
size_t nbytes = bufVec.size();
uint8_t* buf = bufVec.data();
uint8_t* endbuf;
uint32_t t = 0;
/* handle any previously buffered bytes */
if (this->nbuf != 0)
{
while (this->nbuf != 0 && nbytes != 0)
{
*buf ^= (this->sbuf >> (32 - this->nbuf)) & 0xFF;
this->mbuf ^= *buf << (32 - this->nbuf);
++buf;
this->nbuf -= 8;
--nbytes;
}
if (this->nbuf != 0) /* not a whole word yet */
return;
/* LFSR already cycled */
this->macfunc(this->mbuf);
/* handle any previously buffered bytes */
if (this->nbuf != 0) {
while (this->nbuf != 0 && nbytes != 0) {
*buf ^= (this->sbuf >> (32 - this->nbuf)) & 0xFF;
this->mbuf ^= *buf << (32 - this->nbuf);
++buf;
this->nbuf -= 8;
--nbytes;
}
if (this->nbuf != 0) /* not a whole word yet */
return;
/* LFSR already cycled */
this->macfunc(this->mbuf);
}
/* handle whole words */
endbuf = &buf[nbytes & ~((uint32_t)0x03)];
while (buf < endbuf)
{
this->cycle();
t = BYTE2WORD(buf) ^ this->sbuf;
this->macfunc(t);
WORD2BYTE(t, buf);
buf += 4;
}
/* handle whole words */
endbuf = &buf[nbytes & ~((uint32_t)0x03)];
while (buf < endbuf) {
this->cycle();
t = BYTE2WORD(buf) ^ this->sbuf;
this->macfunc(t);
WORD2BYTE(t, buf);
buf += 4;
}
/* handle any trailing bytes */
nbytes &= 0x03;
if (nbytes != 0)
{
this->cycle();
this->mbuf = 0;
this->nbuf = 32;
while (this->nbuf != 0 && nbytes != 0)
{
*buf ^= (this->sbuf >> (32 - this->nbuf)) & 0xFF;
this->mbuf ^= *buf << (32 - this->nbuf);
++buf;
this->nbuf -= 8;
--nbytes;
}
/* handle any trailing bytes */
nbytes &= 0x03;
if (nbytes != 0) {
this->cycle();
this->mbuf = 0;
this->nbuf = 32;
while (this->nbuf != 0 && nbytes != 0) {
*buf ^= (this->sbuf >> (32 - this->nbuf)) & 0xFF;
this->mbuf ^= *buf << (32 - this->nbuf);
++buf;
this->nbuf -= 8;
--nbytes;
}
}
}
void Shannon::finish(std::vector<uint8_t>& bufVec)
{
size_t nbytes = bufVec.size();
uint8_t* buf = bufVec.data();
int i;
void Shannon::finish(std::vector<uint8_t>& bufVec) {
size_t nbytes = bufVec.size();
uint8_t* buf = bufVec.data();
int i;
/* handle any previously buffered bytes */
if (this->nbuf != 0)
{
/* LFSR already cycled */
this->macfunc(this->mbuf);
}
/* handle any previously buffered bytes */
if (this->nbuf != 0) {
/* LFSR already cycled */
this->macfunc(this->mbuf);
}
/* perturb the MAC to mark end of input.
/* perturb the MAC to mark end of input.
* Note that only the stream register is updated, not the CRC. This is an
* action that can't be duplicated by passing in plaintext, hence
* defeating any kind of extension attack.
*/
this->cycle();
ADDKEY(INITKONST ^ (this->nbuf << 3));
this->nbuf = 0;
/* now add the CRC to the stream register and diffuse it */
for (i = 0; i < N; ++i)
this->R[i] ^= this->CRC[i];
this->diffuse();
/* produce output from the stream buffer */
while (nbytes > 0) {
this->cycle();
ADDKEY(INITKONST ^ (this->nbuf << 3));
this->nbuf = 0;
/* now add the CRC to the stream register and diffuse it */
for (i = 0; i < N; ++i)
this->R[i] ^= this->CRC[i];
this->diffuse();
/* produce output from the stream buffer */
while (nbytes > 0)
{
this->cycle();
if (nbytes >= 4)
{
WORD2BYTE(this->sbuf, buf);
nbytes -= 4;
buf += 4;
}
else
{
for (i = 0; i < nbytes; ++i)
buf[i] = Byte(this->sbuf, i);
break;
}
if (nbytes >= 4) {
WORD2BYTE(this->sbuf, buf);
nbytes -= 4;
buf += 4;
} else {
for (i = 0; i < nbytes; ++i)
buf[i] = Byte(this->sbuf, i);
break;
}
}
}

View File

@@ -2,16 +2,15 @@
#include <type_traits> // for remove_extent_t
#ifndef _WIN32
#include <arpa/inet.h>
#endif
#include "BellLogger.h" // for AbstractLogger
#include "Logger.h" // for CSPOT_LOG
#include "Packet.h" // for Packet, cspot
#include "PlainConnection.h" // for PlainConnection
#include "Shannon.h" // for Shannon
#include "Utils.h" // for pack, extract
#ifndef _WIN32
#include <arpa/inet.h>
#endif
using namespace cspot;

View File

@@ -1,18 +1,19 @@
#include "SpircHandler.h"
#include <cstdint> // for uint8_t
#include <memory> // for shared_ptr, make_unique, unique_ptr
#include <type_traits> // for remove_extent_t
#include <utility> // for move
#include <cstdint> // for uint8_t
#include <memory> // for shared_ptr, make_unique, unique_ptr
#include <type_traits> // for remove_extent_t
#include <utility> // for move
#include "BellLogger.h" // for AbstractLogger
#include "CSpotContext.h" // for Context::ConfigState, Context (ptr only)
#include "Logger.h" // for CSPOT_LOG
#include "MercurySession.h" // for MercurySession, MercurySession::Response
#include "NanoPBHelper.h" // for pbDecode
#include "Packet.h" // for cspot
#include "PlaybackState.h" // for PlaybackState, PlaybackState::State
#include "TrackPlayer.h" // for TrackPlayer
#include "BellLogger.h" // for AbstractLogger
#include "CSpotContext.h" // for Context::ConfigState, Context (ptr only)
#include "Logger.h" // for CSPOT_LOG
#include "MercurySession.h" // for MercurySession, MercurySession::Response
#include "NanoPBHelper.h" // for pbDecode
#include "Packet.h" // for cspot
#include "PlaybackState.h" // for PlaybackState, PlaybackState::State
#include "TrackPlayer.h" // for TrackPlayer
#include "TrackQueue.h"
#include "TrackReference.h" // for TrackReference
#include "Utils.h" // for stringHexToBytes
#include "pb_decode.h" // for pb_release
@@ -20,38 +21,30 @@
using namespace cspot;
SpircHandler::SpircHandler(std::shared_ptr<cspot::Context> ctx)
: playbackState(ctx) {
auto isAiringCallback = [this]() {
return !(isNextTrackPreloaded || isRequestedFromLoad);
};
SpircHandler::SpircHandler(std::shared_ptr<cspot::Context> ctx) {
this->playbackState = std::make_shared<PlaybackState>(ctx);
this->trackQueue = std::make_shared<cspot::TrackQueue>(ctx, playbackState);
auto EOFCallback = [this]() {
auto ref = this->playbackState.getNextTrackRef();
if (!isNextTrackPreloaded && !isRequestedFromLoad && ref != nullptr) {
isNextTrackPreloaded = true;
auto trackRef = TrackReference::fromTrackRef(ref);
this->trackPlayer->loadTrackFromRef(trackRef, 0, true);
}
if (ref == nullptr) {
sendEvent(EventType::DEPLETED);
}
};
auto trackLoadedCallback = [this]() {
this->currentTrackInfo = this->trackPlayer->getCurrentTrackInfo();
if (isRequestedFromLoad) {
sendEvent(EventType::PLAYBACK_START, (int)nextTrackPosition);
setPause(false);
if (trackQueue->isFinished()) {
sendEvent(EventType::DEPLETED);
}
};
auto trackLoadedCallback = [this](std::shared_ptr<QueuedTrack> track) {
playbackState->setPlaybackState(PlaybackState::State::Playing);
playbackState->updatePositionMs(track->requestedPosition);
this->notify();
// Send playback start event, unpause
sendEvent(EventType::PLAYBACK_START, (int) track->requestedPosition);
sendEvent(EventType::PLAY_PAUSE, false);
};
this->ctx = ctx;
this->trackPlayer = std::make_shared<TrackPlayer>(ctx, isAiringCallback, EOFCallback, trackLoadedCallback);
this->trackPlayer = std::make_shared<TrackPlayer>(
ctx, trackQueue, EOFCallback, trackLoadedCallback);
// Subscribe to mercury on session ready
ctx->session->setConnectedHandler([this]() { this->subscribeToMercury(); });
@@ -82,105 +75,80 @@ void SpircHandler::subscribeToMercury() {
subscriptionLambda);
}
void SpircHandler::loadTrackFromURI(const std::string& uri) {
// {track/episode}:{gid}
bool isEpisode = uri.find("episode:") != std::string::npos;
auto gid = stringHexToBytes(uri.substr(uri.find(":") + 1));
auto trackRef = TrackReference::fromGID(gid, isEpisode);
void SpircHandler::loadTrackFromURI(const std::string& uri) {}
isRequestedFromLoad = true;
isNextTrackPreloaded = false;
void SpircHandler::notifyAudioReachedPlayback() {
int offset = 0;
playbackState.setActive(true);
// get HEAD track
auto currentTrack = trackQueue->consumeTrack(nullptr, offset);
auto playbackRef = playbackState.getCurrentTrackRef();
// Do not execute when meta is already updated
if (trackQueue->notifyPending) {
trackQueue->notifyPending = false;
if (playbackRef != nullptr) {
playbackState.updatePositionMs(playbackState.remoteFrame.state.position_ms);
playbackState->updatePositionMs(currentTrack->requestedPosition);
auto ref = TrackReference::fromTrackRef(playbackRef);
this->trackPlayer->loadTrackFromRef(
ref, playbackState.remoteFrame.state.position_ms, true);
playbackState.setPlaybackState(PlaybackState::State::Loading);
this->nextTrackPosition = playbackState.remoteFrame.state.position_ms;
}
this->notify();
}
void SpircHandler::notifyAudioReachedPlayback() {
if (isRequestedFromLoad || isNextTrackPreloaded) {
playbackState.updatePositionMs(nextTrackPosition);
playbackState.setPlaybackState(PlaybackState::State::Playing);
// Reset position in queued track
currentTrack->requestedPosition = 0;
} else {
setPause(true);
}
trackQueue->skipTrack(TrackQueue::SkipDirection::NEXT, false);
playbackState->updatePositionMs(0);
isRequestedFromLoad = false;
if (isNextTrackPreloaded) {
isNextTrackPreloaded = false;
playbackState.nextTrack();
nextTrackPosition = 0;
// we moved to next track, re-acquire currentTrack again
currentTrack = trackQueue->consumeTrack(nullptr, offset);
}
this->notify();
sendEvent(EventType::TRACK_INFO, this->trackPlayer->getCurrentTrackInfo());
sendEvent(EventType::TRACK_INFO, currentTrack->trackInfo);
}
void SpircHandler::updatePositionMs(uint32_t position) {
playbackState.updatePositionMs(position);
notify();
playbackState->updatePositionMs(position);
notify();
}
void SpircHandler::disconnect() {
this->trackPlayer->stopTrack();
this->trackQueue->stopTask();
this->trackPlayer->resetState();
this->ctx->session->disconnect();
}
void SpircHandler::handleFrame(std::vector<uint8_t>& data) {
pb_release(Frame_fields, &playbackState.remoteFrame);
pbDecode(playbackState.remoteFrame, Frame_fields, data);
// Decode received spirc frame
playbackState->decodeRemoteFrame(data);
switch (playbackState.remoteFrame.typ) {
switch (playbackState->remoteFrame.typ) {
case MessageType_kMessageTypeNotify: {
CSPOT_LOG(debug, "Notify frame");
// Pause the playback if another player took control
if (playbackState.isActive() &&
playbackState.remoteFrame.device_state.is_active) {
if (playbackState->isActive() &&
playbackState->remoteFrame.device_state.is_active) {
CSPOT_LOG(debug, "Another player took control, pausing playback");
playbackState.setActive(false);
this->trackPlayer->stopTrack();
playbackState->setActive(false);
this->trackPlayer->stop();
sendEvent(EventType::DISC);
}
break;
}
case MessageType_kMessageTypeSeek: {
/* If next track is already downloading, we can't seek in the current one anymore. Also,
* when last track has been reached, we has to restart as we can't tell the difference */
if ((!isNextTrackPreloaded && this->playbackState.getNextTrackRef()) || isRequestedFromLoad) {
CSPOT_LOG(debug, "Seek command while streaming current");
playbackState.updatePositionMs(playbackState.remoteFrame.position);
trackPlayer->seekMs(playbackState.remoteFrame.position);
sendEvent(EventType::SEEK, (int)playbackState.remoteFrame.position);
} else {
CSPOT_LOG(debug, "Seek command while streaming next or before started");
isRequestedFromLoad = true;
isNextTrackPreloaded = false;
auto ref = TrackReference::fromTrackRef(playbackState.getCurrentTrackRef());
this->trackPlayer->loadTrackFromRef(ref, playbackState.remoteFrame.position, true);
this->nextTrackPosition = playbackState.remoteFrame.position;
}
this->trackPlayer->seekMs(playbackState->remoteFrame.position);
playbackState->updatePositionMs(playbackState->remoteFrame.position);
notify();
sendEvent(EventType::SEEK, (int)playbackState->remoteFrame.position);
//sendEvent(EventType::FLUSH);
break;
}
case MessageType_kMessageTypeVolume:
playbackState.setVolume(playbackState.remoteFrame.volume);
playbackState->setVolume(playbackState->remoteFrame.volume);
this->notify();
sendEvent(EventType::VOLUME, (int)playbackState.remoteFrame.volume);
sendEvent(EventType::VOLUME, (int)playbackState->remoteFrame.volume);
break;
case MessageType_kMessageTypePause:
setPause(true);
@@ -197,44 +165,51 @@ void SpircHandler::handleFrame(std::vector<uint8_t>& data) {
sendEvent(EventType::PREV);
break;
case MessageType_kMessageTypeLoad: {
CSPOT_LOG(debug, "Load frame!");
isRequestedFromLoad = true;
isNextTrackPreloaded = false;
this->trackPlayer->start();
playbackState.setActive(true);
playbackState.updateTracks();
CSPOT_LOG(debug, "Load frame %d!", playbackState->remoteTracks.size());
auto playbackRef = playbackState.getCurrentTrackRef();
if (playbackRef != nullptr) {
playbackState.updatePositionMs(
playbackState.remoteFrame.state.position_ms);
auto ref = TrackReference::fromTrackRef(playbackRef);
this->trackPlayer->loadTrackFromRef(
ref, playbackState.remoteFrame.state.position_ms, true);
playbackState.setPlaybackState(PlaybackState::State::Loading);
this->nextTrackPosition = playbackState.remoteFrame.state.position_ms;
if (playbackState->remoteTracks.size() == 0) {
CSPOT_LOG(info, "No tracks in frame, stopping playback");
break;
}
playbackState->setActive(true);
playbackState->updatePositionMs(playbackState->remoteFrame.position);
playbackState->setPlaybackState(PlaybackState::State::Playing);
playbackState->syncWithRemote();
// Update track list in case we have a new one
trackQueue->updateTracks(playbackState->remoteFrame.state.position_ms,
true);
this->notify();
// Stop the current track, if any
trackPlayer->resetState();
break;
}
case MessageType_kMessageTypeReplace: {
CSPOT_LOG(debug, "Got replace frame");
playbackState.updateTracks();
playbackState->syncWithRemote();
trackQueue->updateTracks(playbackState->remoteFrame.state.position_ms,
false);
this->notify();
trackPlayer->resetState();
sendEvent(EventType::FLUSH);
break;
}
case MessageType_kMessageTypeShuffle: {
CSPOT_LOG(debug, "Got shuffle frame");
playbackState.setShuffle(playbackState.remoteFrame.state.shuffle);
this->notify();
break;
}
case MessageType_kMessageTypeRepeat: {
CSPOT_LOG(debug, "Got repeat frame");
playbackState.setRepeat(playbackState.remoteFrame.state.repeat);
this->notify();
break;
}
@@ -244,7 +219,7 @@ void SpircHandler::handleFrame(std::vector<uint8_t>& data) {
}
void SpircHandler::setRemoteVolume(int volume) {
playbackState.setVolume(volume);
playbackState->setVolume(volume);
notify();
}
@@ -252,32 +227,34 @@ void SpircHandler::notify() {
this->sendCmd(MessageType_kMessageTypeNotify);
}
void SpircHandler::nextSong() {
if (playbackState.nextTrack()) {
isRequestedFromLoad = true;
isNextTrackPreloaded = false;
auto ref = TrackReference::fromTrackRef(playbackState.getCurrentTrackRef());
this->trackPlayer->loadTrackFromRef(ref, 0, true);
void SpircHandler::skipSong(TrackQueue::SkipDirection dir) {
if (trackQueue->skipTrack(dir)) {
playbackState->setPlaybackState(PlaybackState::State::Playing);
notify();
// Reset track state
trackPlayer->resetState();
sendEvent(EventType::PLAY_PAUSE, false);
} else {
sendEvent(EventType::FLUSH);
playbackState.updatePositionMs(0);
trackPlayer->stopTrack();
playbackState->setPlaybackState(PlaybackState::State::Paused);
playbackState->updatePositionMs(0);
notify();
sendEvent(EventType::PLAY_PAUSE, true);
}
this->nextTrackPosition = 0;
notify();
sendEvent(EventType::FLUSH);
}
void SpircHandler::nextSong() {
skipSong(TrackQueue::SkipDirection::NEXT);
}
void SpircHandler::previousSong() {
playbackState.prevTrack();
isRequestedFromLoad = true;
isNextTrackPreloaded = false;
sendEvent(EventType::PREV);
auto ref = TrackReference::fromTrackRef(playbackState.getCurrentTrackRef());
this->trackPlayer->loadTrackFromRef(ref, 0, true);
this->nextTrackPosition = 0;
notify();
skipSong(TrackQueue::SkipDirection::PREV);
}
std::shared_ptr<TrackPlayer> SpircHandler::getTrackPlayer() {
@@ -286,7 +263,7 @@ std::shared_ptr<TrackPlayer> SpircHandler::getTrackPlayer() {
void SpircHandler::sendCmd(MessageType typ) {
// Serialize current player state
auto encodedFrame = playbackState.encodeCurrentFrame(typ);
auto encodedFrame = playbackState->encodeCurrentFrame(typ);
auto responseLambda = [=](MercurySession::Response& res) {
};
@@ -302,11 +279,11 @@ void SpircHandler::setEventHandler(EventHandler handler) {
void SpircHandler::setPause(bool isPaused) {
if (isPaused) {
CSPOT_LOG(debug, "External pause command");
playbackState.setPlaybackState(PlaybackState::State::Paused);
playbackState->setPlaybackState(PlaybackState::State::Paused);
} else {
CSPOT_LOG(debug, "External play command");
playbackState.setPlaybackState(PlaybackState::State::Playing);
playbackState->setPlaybackState(PlaybackState::State::Playing);
}
notify();
sendEvent(EventType::PLAY_PAUSE, isPaused);

View File

@@ -1,25 +1,24 @@
#include "TimeProvider.h"
#ifndef _WIN32
#include <arpa/inet.h>
#endif
#include "BellLogger.h" // for AbstractLogger
#include "Logger.h" // for CSPOT_LOG
#include "Utils.h" // for extract, getCurrentTimestamp
#ifndef _WIN32
#include <arpa/inet.h>
#endif
using namespace cspot;
TimeProvider::TimeProvider() {
}
TimeProvider::TimeProvider() {}
void TimeProvider::syncWithPingPacket(const std::vector<uint8_t>& pongPacket) {
CSPOT_LOG(debug, "Time synced with spotify servers");
// Spotify's timestamp is in seconds since unix time - convert to millis.
uint64_t remoteTimestamp = ((uint64_t) ntohl(extract<uint32_t>(pongPacket, 0))) * 1000;
this->timestampDiff = remoteTimestamp - getCurrentTimestamp();
CSPOT_LOG(debug, "Time synced with spotify servers");
// Spotify's timestamp is in seconds since unix time - convert to millis.
uint64_t remoteTimestamp =
((uint64_t)ntohl(extract<uint32_t>(pongPacket, 0))) * 1000;
this->timestampDiff = remoteTimestamp - getCurrentTimestamp();
}
unsigned long long TimeProvider::getSyncedTimestamp() {
return getCurrentTimestamp() + this->timestampDiff;
return getCurrentTimestamp() + this->timestampDiff;
}

View File

@@ -1,18 +1,26 @@
#include "TrackPlayer.h"
#include <mutex> // for mutex, scoped_lock
#include <string> // for string
#include <type_traits> // for remove_extent_t
#include <vector> // for vector, vector<>::value_type
#include <mutex> // for mutex, scoped_lock
#include <string> // for string
#include <type_traits> // for remove_extent_t
#include <vector> // for vector, vector<>::value_type
#include "BellLogger.h" // for AbstractLogger
#include "BellUtils.h" // for BELL_SLEEP_MS
#include "CDNTrackStream.h" // for CDNTrackStream, CDNTrackStream::TrackInfo
#include "Logger.h" // for CSPOT_LOG
#include "Packet.h" // for cspot
#include "TrackProvider.h" // for TrackProvider
#include "TrackQueue.h" // for CDNTrackStream, CDNTrackStream::TrackInfo
#include "WrappedSemaphore.h" // for WrappedSemaphore
#ifdef BELL_VORBIS_FLOAT
#define VORBIS_SEEK(file, position) (ov_time_seek(file, (double)position / 1000))
#define VORBIS_READ(file, buffer, bufferSize, section) (ov_read(file, buffer, bufferSize, 0, 2, 1, section))
#else
#define VORBIS_SEEK(file, position) (ov_time_seek(file, position))
#define VORBIS_READ(file, buffer, bufferSize, section) \
(ov_read(file, buffer, bufferSize, section))
#endif
namespace cspot {
struct Context;
struct TrackReference;
@@ -38,13 +46,14 @@ static long vorbisTellCb(TrackPlayer* self) {
return self->_vorbisTell();
}
TrackPlayer::TrackPlayer(std::shared_ptr<cspot::Context> ctx, isAiringCallback isAiring, EOFCallback eof, TrackLoadedCallback trackLoaded)
: bell::Task("cspot_player", 48 * 1024, 5, 1) {
TrackPlayer::TrackPlayer(std::shared_ptr<cspot::Context> ctx,
std::shared_ptr<cspot::TrackQueue> trackQueue,
EOFCallback eof, TrackLoadedCallback trackLoaded)
: bell::Task("cspot_player", 32 * 1024, 5, 1) {
this->ctx = ctx;
this->isAiring = isAiring;
this->eofCallback = eof;
this->trackLoaded = trackLoaded;
this->trackProvider = std::make_shared<cspot::TrackProvider>(ctx);
this->trackQueue = trackQueue;
this->playbackSemaphore = std::make_unique<bell::WrappedSemaphore>(5);
// Initialize vorbis callbacks
@@ -55,9 +64,6 @@ TrackPlayer::TrackPlayer(std::shared_ptr<cspot::Context> ctx, isAiringCallback i
(decltype(ov_callbacks::close_func))&vorbisCloseCb,
(decltype(ov_callbacks::tell_func))&vorbisTellCb,
};
isRunning = true;
startTask();
}
TrackPlayer::~TrackPlayer() {
@@ -65,123 +71,192 @@ TrackPlayer::~TrackPlayer() {
std::scoped_lock lock(runningMutex);
}
void TrackPlayer::loadTrackFromRef(TrackReference& ref, size_t positionMs,
bool startAutomatically) {
this->playbackPosition = positionMs;
this->autoStart = startAutomatically;
auto nextTrack = trackProvider->loadFromTrackRef(ref);
stopTrack();
this->sequence++;
this->currentTrackStream = nextTrack;
this->playbackSemaphore->give();
void TrackPlayer::start() {
if (!isRunning) {
isRunning = true;
startTask();
}
}
void TrackPlayer::stopTrack() {
void TrackPlayer::stop() {
isRunning = false;
resetState();
std::scoped_lock lock(runningMutex);
}
void TrackPlayer::resetState() {
// Mark for reset
this->pendingReset = true;
this->currentSongPlaying = false;
std::scoped_lock lock(playbackMutex);
std::scoped_lock lock(dataOutMutex);
CSPOT_LOG(info, "Resetting state");
}
void TrackPlayer::seekMs(size_t ms) {
std::scoped_lock lock(seekMutex);
#ifdef BELL_VORBIS_FLOAT
ov_time_seek(&vorbisFile, (double)ms / 1000);
#else
ov_time_seek(&vorbisFile, ms);
#endif
if (inFuture) {
// We're in the middle of the next track, so we need to reset the player in order to seek
resetState();
}
CSPOT_LOG(info, "Seeking...");
this->pendingSeekPositionMs = ms;
}
void TrackPlayer::runTask() {
std::scoped_lock lock(runningMutex);
std::shared_ptr<QueuedTrack> track, newTrack = nullptr;
int trackOffset = 0;
bool eof = false;
bool endOfQueueReached = false;
while (isRunning) {
this->playbackSemaphore->twait(100);
if (this->currentTrackStream == nullptr) {
// Ensure we even have any tracks to play
if (!this->trackQueue->hasTracks() ||
(endOfQueueReached && trackQueue->isFinished())) {
this->trackQueue->playableSemaphore->twait(300);
continue;
}
CSPOT_LOG(info, "Player received a track, waiting for it to be ready...");
// when track changed many times and very quickly, we are stuck on never-given semaphore
while (this->currentTrackStream->trackReady->twait(250));
CSPOT_LOG(info, "Got track");
// Last track was interrupted, reset to default
if (pendingReset) {
track = nullptr;
pendingReset = false;
inFuture = false;
}
if (this->currentTrackStream->status == CDNTrackStream::Status::FAILED) {
CSPOT_LOG(error, "Track failed to load, skipping it");
this->currentTrackStream = nullptr;
this->eofCallback();
endOfQueueReached = false;
// Wait 800ms. If next reset is requested in meantime, restart the queue.
// Gets rid of excess actions during rapid queueing
BELL_SLEEP_MS(50);
if (pendingReset) {
continue;
}
this->currentSongPlaying = true;
newTrack = trackQueue->consumeTrack(track, trackOffset);
this->trackLoaded();
if (newTrack == nullptr) {
if (trackOffset == -1) {
// Reset required
track = nullptr;
}
this->playbackMutex.lock();
int32_t r = ov_open_callbacks(this, &vorbisFile, NULL, 0, vorbisCallbacks);
if (playbackPosition > 0) {
#ifdef BELL_VORBIS_FLOAT
ov_time_seek(&vorbisFile, (double)playbackPosition / 1000);
#else
ov_time_seek(&vorbisFile, playbackPosition);
#endif
BELL_SLEEP_MS(100);
continue;
}
bool eof = false;
track = newTrack;
while (!eof && currentSongPlaying) {
seekMutex.lock();
#ifdef BELL_VORBIS_FLOAT
long ret = ov_read(&vorbisFile, (char*)&pcmBuffer[0], pcmBuffer.size(),
0, 2, 1, &currentSection);
#else
long ret = ov_read(&vorbisFile, (char*)&pcmBuffer[0], pcmBuffer.size(),
&currentSection);
#endif
seekMutex.unlock();
if (ret == 0) {
CSPOT_LOG(info, "EOF");
// and done :)
eof = true;
} else if (ret < 0) {
CSPOT_LOG(error, "An error has occured in the stream %d", ret);
currentSongPlaying = false;
} else {
inFuture = trackOffset > 0;
if (this->dataCallback != nullptr) {
auto toWrite = ret;
if (track->state != QueuedTrack::State::READY) {
track->loadedSemaphore->twait(5000);
while (!eof && currentSongPlaying && toWrite > 0) {
auto written =
dataCallback(pcmBuffer.data() + (ret - toWrite), toWrite,
this->currentTrackStream->trackInfo.trackId, this->sequence);
if (written == 0) {
BELL_SLEEP_MS(50);
if (track->state != QueuedTrack::State::READY) {
CSPOT_LOG(error, "Track failed to load, skipping it");
this->eofCallback();
continue;
}
}
CSPOT_LOG(info, "Got track ID=%s", track->identifier.c_str());
currentSongPlaying = true;
{
std::scoped_lock lock(playbackMutex);
currentTrackStream = track->getAudioFile();
// Open the stream
currentTrackStream->openStream();
if (pendingReset || !currentSongPlaying) {
continue;
}
if (trackOffset == 0 && pendingSeekPositionMs == 0) {
this->trackLoaded(track);
}
int32_t r =
ov_open_callbacks(this, &vorbisFile, NULL, 0, vorbisCallbacks);
if (pendingSeekPositionMs > 0) {
track->requestedPosition = pendingSeekPositionMs;
}
if (track->requestedPosition > 0) {
VORBIS_SEEK(&vorbisFile, track->requestedPosition);
}
eof = false;
CSPOT_LOG(info, "Playing");
while (!eof && currentSongPlaying) {
// Execute seek if needed
if (pendingSeekPositionMs > 0) {
uint32_t seekPosition = pendingSeekPositionMs;
// Reset the pending seek position
pendingSeekPositionMs = 0;
// Seek to the new position
VORBIS_SEEK(&vorbisFile, seekPosition);
}
long ret = VORBIS_READ(&vorbisFile, (char*)&pcmBuffer[0],
pcmBuffer.size(), &currentSection);
if (ret == 0) {
CSPOT_LOG(info, "EOF");
// and done :)
eof = true;
} else if (ret < 0) {
CSPOT_LOG(error, "An error has occured in the stream %d", ret);
currentSongPlaying = false;
} else {
if (this->dataCallback != nullptr) {
auto toWrite = ret;
while (!eof && currentSongPlaying && !pendingReset && toWrite > 0) {
int written = 0;
{
std::scoped_lock dataOutLock(dataOutMutex);
// If reset happened during playback, return
if (!currentSongPlaying || pendingReset)
break;
written =
dataCallback(pcmBuffer.data() + (ret - toWrite), toWrite, track->identifier);
}
if (written == 0) {
BELL_SLEEP_MS(50);
}
toWrite -= written;
}
toWrite -= written;
}
}
}
}
ov_clear(&vorbisFile);
ov_clear(&vorbisFile);
// With very large buffers, track N+1 can be downloaded while N has not aired yet and
// if we continue, the currentTrackStream will be emptied, causing a crash in
// notifyAudioReachedPlayback when it will look for trackInfo. A busy loop is never
// ideal, but this low impact, infrequent and more simple than yet another semaphore
while (currentSongPlaying && !isAiring()) {
BELL_SLEEP_MS(100);
}
CSPOT_LOG(info, "Playing done");
// always move back to LOADING (ensure proper seeking after last track has been loaded)
this->currentTrackStream.reset();
this->playbackMutex.unlock();
// always move back to LOADING (ensure proper seeking after last track has been loaded)
currentTrackStream = nullptr;
}
if (eof) {
if (trackQueue->isFinished()) {
endOfQueueReached = true;
}
this->eofCallback();
}
}
@@ -226,10 +301,6 @@ long TrackPlayer::_vorbisTell() {
return this->currentTrackStream->getPosition();
}
CDNTrackStream::TrackInfo TrackPlayer::getCurrentTrackInfo() {
return this->currentTrackStream->trackInfo;
}
void TrackPlayer::setDataCallback(DataCallback callback) {
this->dataCallback = callback;
}

View File

@@ -8,7 +8,7 @@
#include <type_traits> // for enable_if<>::type
#include <chrono>
#ifndef _WIN32
#include <arpa/inet.h>
#include <netdb.h>
#endif
unsigned long long getCurrentTimestamp() {