|  | @@ -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() {
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +}
 |