mirror of
https://github.com/sle118/squeezelite-esp32.git
synced 2025-12-09 13:07:03 +03:00
new cspot/bell
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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, ¤tSection);
|
||||
#else
|
||||
long ret = ov_read(&vorbisFile, (char*)&pcmBuffer[0], pcmBuffer.size(),
|
||||
¤tSection);
|
||||
#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(), ¤tSection);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user