ChunkedByteStream.cpp 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. #include "ChunkedByteStream.h"
  2. ChunkedByteStream::ChunkedByteStream(std::shared_ptr<MercuryManager> manager) {
  3. this->mercuryManager = manager;
  4. this->pos = 167; // spotify header size
  5. }
  6. void ChunkedByteStream::setFileInfo(std::vector<uint8_t> &fileId, std::vector<uint8_t> &audioKey) {
  7. this->audioKey = audioKey;
  8. this->fileId = fileId;
  9. }
  10. void ChunkedByteStream::setEnableLoadAhead(bool loadAhead) {
  11. this->loadAheadEnabled = loadAhead;
  12. }
  13. void ChunkedByteStream::fetchFileInformation() {
  14. std::shared_ptr<AudioChunk> beginChunk = mercuryManager->fetchAudioChunk(fileId, audioKey, 0, 0x4000);
  15. beginChunk->keepInMemory = true;
  16. while (beginChunk->isHeaderFileSizeLoadedSemaphore->twait() != 0);
  17. this->fileSize = beginChunk->headerFileSize;
  18. chunks.push_back(beginChunk);
  19. auto startPosition = (this->fileSize / 4) - 0x1000;
  20. // AES block size is 16, so the index must be divisible by it
  21. while ((startPosition * 4) % 16 != 0)
  22. startPosition++; // ik, ugly lol
  23. auto endChunk = mercuryManager->fetchAudioChunk(fileId, audioKey, startPosition, fileSize / 4);
  24. endChunk->keepInMemory = true;
  25. chunks.push_back(endChunk);
  26. requestChunk(0);
  27. }
  28. std::shared_ptr<AudioChunk> ChunkedByteStream::getChunkForPosition(size_t position) {
  29. // Find chunks that fit in requested position
  30. for (auto chunk: chunks) {
  31. if (chunk->startPosition <= position && chunk->endPosition > position) {
  32. return chunk;
  33. }
  34. }
  35. return nullptr;
  36. }
  37. std::shared_ptr<AudioChunk> ChunkedByteStream::requestChunk(uint16_t position) {
  38. auto chunk = this->mercuryManager->fetchAudioChunk(this->fileId, this->audioKey, position);
  39. // Store a reference internally
  40. chunks.push_back(chunk);
  41. return chunk;
  42. }
  43. size_t ChunkedByteStream::read(uint8_t *buf, size_t nbytes) {
  44. std::scoped_lock lock(this->readMutex);
  45. auto chunk = getChunkForPosition(pos);
  46. uint16_t chunkIndex = this->pos / AUDIO_CHUNK_SIZE;
  47. if (loadAheadEnabled) {
  48. for (auto it = chunks.begin(); it != chunks.end();) {
  49. if (((*it)->endPosition<pos || (*it)->startPosition>(pos + 2 * AUDIO_CHUNK_SIZE)) && !(*it)->keepInMemory) {
  50. it = chunks.erase(it);
  51. } else {
  52. it++;
  53. }
  54. }
  55. }
  56. // Request chunk if does not exist
  57. if (chunk == nullptr) {
  58. BELL_LOG(info, "cspot", "Chunk not found, requesting %d", chunkIndex);
  59. chunk = this->requestChunk(chunkIndex);
  60. }
  61. if (chunk != nullptr) {
  62. // Wait for chunk if not loaded yet
  63. if (!chunk->isLoaded && !chunk->isFailed) {
  64. chunk->isLoadedSemaphore->wait();
  65. }
  66. if (chunk->isFailed) return 0;
  67. // Attempt to read from chunk
  68. auto read = attemptRead(buf, nbytes, chunk);
  69. pos += read;
  70. auto nextChunkPos = ((chunkIndex + 1) * AUDIO_CHUNK_SIZE) + 1;
  71. if (loadAheadEnabled && nextChunkPos < fileSize) {
  72. auto nextChunk = getChunkForPosition(nextChunkPos);
  73. if (nextChunk == nullptr) {
  74. // Request next chunk
  75. this->requestChunk(chunkIndex + 1);
  76. }
  77. }
  78. return read;
  79. }
  80. return 0;
  81. }
  82. size_t ChunkedByteStream::attemptRead(uint8_t *buffer, size_t bytes, std::shared_ptr<AudioChunk> chunk) {
  83. //if (!chunk->isLoaded || chunk->isFailed || chunk->startPosition >= pos || chunk->endPosition < pos) return 0;
  84. // Calculate how many bytes we can read from chunk
  85. auto offset = pos - chunk->startPosition;
  86. auto toRead = bytes;
  87. if (toRead > chunk->decryptedData.size() - offset) {
  88. toRead = chunk->decryptedData.size() - offset;
  89. }
  90. // Copy data
  91. memcpy(buffer, chunk->decryptedData.data() + offset, toRead);
  92. return toRead;
  93. }
  94. void ChunkedByteStream::seek(size_t nbytes) {
  95. std::scoped_lock lock(this->readMutex);
  96. pos = nbytes;
  97. if (getChunkForPosition(this->pos) == nullptr) {
  98. // Seeking might look back - therefore we preload some past data
  99. auto startPosition = (this->pos / 4) - (AUDIO_CHUNK_SIZE / 4);
  100. // AES block size is 16, so the index must be divisible by it
  101. while ((startPosition * 4) % 16 != 0)
  102. startPosition++; // ik, ugly lol
  103. this->chunks.push_back(mercuryManager->fetchAudioChunk(fileId, audioKey, startPosition,
  104. startPosition + (AUDIO_CHUNK_SIZE / 4)));
  105. }
  106. }
  107. size_t ChunkedByteStream::skip(size_t nbytes) {
  108. std::scoped_lock lock(this->readMutex);
  109. pos += nbytes;
  110. return pos;
  111. }
  112. size_t ChunkedByteStream::position() {
  113. return pos;
  114. }
  115. size_t ChunkedByteStream::size() {
  116. return fileSize;
  117. }
  118. void ChunkedByteStream::close() {
  119. }