add missing files

This commit is contained in:
Philippe G
2022-01-06 18:56:27 -08:00
parent 9af4cd5b23
commit 052600a45a
2 changed files with 268 additions and 0 deletions

View File

@@ -0,0 +1,115 @@
#ifndef CSPOT_CHUNKEDBYTESTREAM_H
#define CSPOT_CHUNKEDBYTESTREAM_H
#include "ByteStream.h"
#include "BellLogger.h"
#include <memory>
#include "MercuryManager.h"
#include "stdint.h"
class ChunkedByteStream : public bell::ByteStream {
private:
// AES key used for data decryption
std::vector<uint8_t> audioKey;
// Spotify internal fileId
std::vector<uint8_t> fileId;
// Buffer for storing currently read chunks
std::vector<std::shared_ptr<AudioChunk>> chunks;
// Current position of the read pointer
size_t pos = 0;
size_t fileSize = -1;
std::mutex readMutex;
std::atomic<bool> loadAheadEnabled = false;
std::shared_ptr<MercuryManager> mercuryManager;
/**
* Returns an audio chunk for given position.
* @param position requested position
* @return matching audio chunk, or nullptr if no chunk is available
*/
std::shared_ptr<AudioChunk> getChunkForPosition(size_t position);
/**
* Requests a new audio chunk from mercury manager. Returns its structure immediately.
* @param position index of a chunk to request
* @return requested chunk
*/
std::shared_ptr<AudioChunk> requestChunk(uint16_t position);
/**
* Tries to read data from a given audio chunk.
* @param buffer destination buffer
* @param bytes number of bytes to read
* @param chunk `AudioChunk` to read from
* @return number of bytes read
*/
size_t attemptRead(uint8_t *buffer, size_t bytes, std::shared_ptr<AudioChunk> chunk);
public:
ChunkedByteStream(std::shared_ptr<MercuryManager> manager);
~ChunkedByteStream() {};
/**
* Requests first chunk from the file, and then fills file information based on its header
*/
void fetchFileInformation();
/**
* Enables / disables load-ahead of chunks.
* @param loadAhead true to enable load ahead
*/
void setEnableLoadAhead(bool loadAhead);
/**
* Sets information about given spotify file, necessary for chunk request
* @param fileId id of given audio file
* @param audioKey audio key used for decryption
*/
void setFileInfo(std::vector<uint8_t>& fileId, std::vector<uint8_t>& audioKey);
// ---- ByteStream methods ----
/**
* Reads given amount of bytes from stream. Data is OPUS encoded.
* @param buffer buffer to read into
* @param size amount of bytes to read
* @return amount of bytes read
*/
size_t read(uint8_t *buf, size_t nbytes);
/**
* Seeks to given position in stream
* @param pos position to seek to
*/
void seek(size_t pos);
/**
* skip given amount of bytes in stream.
* @param nbytes amount of bytes to skip
*/
size_t skip(size_t nbytes);
/**
* Returns current position in stream.
* @return position
*/
size_t position();
/**
* Returns size of the file
* @return bytes in file
*/
size_t size();
/**
* Close the reader
*/
void close();
};
#endif //CSPOT_CHUNKEDBYTESTREAM_H

View File

@@ -0,0 +1,153 @@
#include "ChunkedByteStream.h"
ChunkedByteStream::ChunkedByteStream(std::shared_ptr<MercuryManager> manager) {
this->mercuryManager = manager;
this->pos = 167; // spotify header size
}
void ChunkedByteStream::setFileInfo(std::vector<uint8_t> &fileId, std::vector<uint8_t> &audioKey) {
this->audioKey = audioKey;
this->fileId = fileId;
}
void ChunkedByteStream::setEnableLoadAhead(bool loadAhead) {
this->loadAheadEnabled = loadAhead;
}
void ChunkedByteStream::fetchFileInformation() {
std::shared_ptr<AudioChunk> beginChunk = mercuryManager->fetchAudioChunk(fileId, audioKey, 0, 0x4000);
beginChunk->keepInMemory = true;
while (beginChunk->isHeaderFileSizeLoadedSemaphore->twait() != 0);
this->fileSize = beginChunk->headerFileSize;
chunks.push_back(beginChunk);
auto startPosition = (this->fileSize / 4) - 0x1000;
// AES block size is 16, so the index must be divisible by it
while ((startPosition * 4) % 16 != 0)
startPosition++; // ik, ugly lol
auto endChunk = mercuryManager->fetchAudioChunk(fileId, audioKey, startPosition, fileSize / 4);
endChunk->keepInMemory = true;
chunks.push_back(endChunk);
}
std::shared_ptr<AudioChunk> ChunkedByteStream::getChunkForPosition(size_t position) {
// Find chunks that fit in requested position
for (auto chunk: chunks) {
if (chunk->startPosition <= position && chunk->endPosition > position) {
return chunk;
}
}
return nullptr;
}
std::shared_ptr<AudioChunk> ChunkedByteStream::requestChunk(uint16_t position) {
auto chunk = this->mercuryManager->fetchAudioChunk(this->fileId, this->audioKey, position);
// Store a reference internally
chunks.push_back(chunk);
return chunk;
}
size_t ChunkedByteStream::read(uint8_t *buf, size_t nbytes) {
std::scoped_lock lock(this->readMutex);
auto chunk = getChunkForPosition(pos);
uint16_t chunkIndex = this->pos / AUDIO_CHUNK_SIZE;
for (auto it = chunks.begin(); it != chunks.end();) {
if (((*it)->endPosition<pos || (*it)->startPosition>(pos + 2 * AUDIO_CHUNK_SIZE)) && !(*it)->keepInMemory) {
it = chunks.erase(it);
} else {
it++;
}
}
// Request chunk if does not exist
if (chunk == nullptr) {
BELL_LOG(info, "cspot", "Chunk not found, requesting %d", chunkIndex);
chunk = this->requestChunk(chunkIndex);
}
if (chunk != nullptr) {
// Wait for chunk if not loaded yet
if (!chunk->isLoaded && !chunk->isFailed) {
chunk->isLoadedSemaphore->wait();
}
if (chunk->isFailed) return 0;
// Attempt to read from chunk
auto read = attemptRead(buf, nbytes, chunk);
pos += read;
auto nextChunkPos = ((chunkIndex + 1) * AUDIO_CHUNK_SIZE) + 1;
if (loadAheadEnabled && nextChunkPos + AUDIO_CHUNK_SIZE < fileSize) {
auto nextChunk = getChunkForPosition(nextChunkPos);
if (nextChunk == nullptr) {
// Request next chunk
this->requestChunk(chunkIndex + 1);
}
}
return read;
}
return 0;
}
size_t ChunkedByteStream::attemptRead(uint8_t *buffer, size_t bytes, std::shared_ptr<AudioChunk> chunk) {
//if (!chunk->isLoaded || chunk->isFailed || chunk->startPosition >= pos || chunk->endPosition < pos) return 0;
// Calculate how many bytes we can read from chunk
auto offset = pos - chunk->startPosition;
auto toRead = bytes;
if (toRead > chunk->decryptedData.size() - offset) {
toRead = chunk->decryptedData.size() - offset;
}
// Copy data
memcpy(buffer, chunk->decryptedData.data() + offset, toRead);
return toRead;
}
void ChunkedByteStream::seek(size_t nbytes) {
std::scoped_lock lock(this->readMutex);
BELL_LOG(info, "cspot", "seeking to %d", nbytes);
pos = nbytes;
if (getChunkForPosition(this->pos) == nullptr) {
// Seeking might look back - therefore we preload some past data
auto startPosition = (this->pos / 4) - (AUDIO_CHUNK_SIZE / 4);
// AES block size is 16, so the index must be divisible by it
while ((startPosition * 4) % 16 != 0)
startPosition++; // ik, ugly lol
this->chunks.push_back(mercuryManager->fetchAudioChunk(fileId, audioKey, startPosition,
startPosition + (AUDIO_CHUNK_SIZE / 4)));
}
}
size_t ChunkedByteStream::skip(size_t nbytes) {
std::scoped_lock lock(this->readMutex);
pos += nbytes;
return pos;
}
size_t ChunkedByteStream::position() {
return pos;
}
size_t ChunkedByteStream::size() {
return fileSize;
}
void ChunkedByteStream::close() {
}