2
0

SpotifyTrack.cpp 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. #include "SpotifyTrack.h"
  2. #include "unistd.h"
  3. #include "MercuryManager.h"
  4. #include <cassert>
  5. #include "CspotAssert.h"
  6. #include "Logger.h"
  7. #include "ConfigJSON.h"
  8. SpotifyTrack::SpotifyTrack(std::shared_ptr<MercuryManager> manager, std::shared_ptr<TrackReference> trackReference, uint32_t position_ms, bool isPaused)
  9. {
  10. this->manager = manager;
  11. this->fileId = std::vector<uint8_t>();
  12. episodeInfo = {};
  13. trackInfo = {};
  14. mercuryCallback trackResponseLambda = [=](std::unique_ptr<MercuryResponse> res) {
  15. this->trackInformationCallback(std::move(res), position_ms, isPaused);
  16. };
  17. mercuryCallback episodeResponseLambda = [=](std::unique_ptr<MercuryResponse> res) {
  18. this->episodeInformationCallback(std::move(res), position_ms, isPaused);
  19. };
  20. if (trackReference->isEpisode)
  21. {
  22. this->reqSeqNum = this->manager->execute(MercuryType::GET, "hm://metadata/3/episode/" + bytesToHexString(trackReference->gid), episodeResponseLambda);
  23. }
  24. else
  25. {
  26. this->reqSeqNum = this->manager->execute(MercuryType::GET, "hm://metadata/3/track/" + bytesToHexString(trackReference->gid), trackResponseLambda);
  27. }
  28. }
  29. SpotifyTrack::~SpotifyTrack()
  30. {
  31. this->manager->unregisterMercuryCallback(this->reqSeqNum);
  32. this->manager->freeAudioKeyCallback();
  33. pb_release(Track_fields, &this->trackInfo);
  34. pb_release(Episode_fields, &this->episodeInfo);
  35. }
  36. bool SpotifyTrack::countryListContains(std::string countryList, std::string country)
  37. {
  38. for (int x = 0; x < countryList.size(); x += 2)
  39. {
  40. if (countryList.substr(x, 2) == country)
  41. {
  42. return true;
  43. }
  44. }
  45. return false;
  46. }
  47. bool SpotifyTrack::canPlayTrack()
  48. {
  49. for (int x = 0; x < trackInfo.restriction_count; x++)
  50. {
  51. if (trackInfo.restriction[x].countries_allowed != nullptr)
  52. {
  53. return countryListContains(std::string(trackInfo.restriction[x].countries_allowed), manager->countryCode);
  54. }
  55. if (trackInfo.restriction[x].countries_forbidden != nullptr)
  56. {
  57. return !countryListContains(std::string(trackInfo.restriction[x].countries_forbidden), manager->countryCode);
  58. }
  59. }
  60. return true;
  61. }
  62. void SpotifyTrack::trackInformationCallback(std::unique_ptr<MercuryResponse> response, uint32_t position_ms, bool isPaused)
  63. {
  64. if (this->fileId.size() != 0)
  65. return;
  66. CSPOT_ASSERT(response->parts.size() > 0, "response->parts.size() must be greater than 0");
  67. pb_release(Track_fields, &trackInfo);
  68. pbDecode(trackInfo, Track_fields, response->parts[0]);
  69. CSPOT_LOG(info, "Track name: %s", trackInfo.name);
  70. CSPOT_LOG(info, "Track duration: %d", trackInfo.duration);
  71. CSPOT_LOG(debug, "trackInfo.restriction.size() = %d", trackInfo.restriction_count);
  72. int altIndex = 0;
  73. while (!canPlayTrack())
  74. {
  75. std::swap(trackInfo.restriction, trackInfo.alternative[altIndex].restriction);
  76. std::swap(trackInfo.restriction_count, trackInfo.alternative[altIndex].restriction_count);
  77. std::swap(trackInfo.file, trackInfo.alternative[altIndex].file);
  78. std::swap(trackInfo.file_count, trackInfo.alternative[altIndex].file_count);
  79. std::swap(trackInfo.gid, trackInfo.alternative[altIndex].gid);
  80. CSPOT_LOG(info, "Trying alternative %d", altIndex);
  81. altIndex++;
  82. if(altIndex > trackInfo.alternative_count) {
  83. // no alternatives for song
  84. return;
  85. }
  86. }
  87. auto trackId = pbArrayToVector(trackInfo.gid);
  88. this->fileId = std::vector<uint8_t>();
  89. for (int x = 0; x < trackInfo.file_count; x++)
  90. {
  91. if (trackInfo.file[x].format == configMan->format)
  92. {
  93. this->fileId = pbArrayToVector(trackInfo.file[x].file_id);
  94. break; // If file found stop searching
  95. }
  96. }
  97. if (trackInfoReceived != nullptr)
  98. {
  99. auto imageId = pbArrayToVector(trackInfo.album.cover_group.image[0].file_id);
  100. TrackInfo simpleTrackInfo = {
  101. .name = std::string(trackInfo.name),
  102. .album = std::string(trackInfo.album.name),
  103. .artist = std::string(trackInfo.artist[0].name),
  104. .imageUrl = "https://i.scdn.co/image/" + bytesToHexString(imageId),
  105. .duration = trackInfo.duration,
  106. };
  107. trackInfoReceived(simpleTrackInfo);
  108. }
  109. this->requestAudioKey(this->fileId, trackId, trackInfo.duration, position_ms, isPaused);
  110. }
  111. void SpotifyTrack::episodeInformationCallback(std::unique_ptr<MercuryResponse> response, uint32_t position_ms, bool isPaused)
  112. {
  113. if (this->fileId.size() != 0)
  114. return;
  115. CSPOT_LOG(debug, "Got to episode");
  116. CSPOT_ASSERT(response->parts.size() > 0, "response->parts.size() must be greater than 0");
  117. pb_release(Episode_fields, &episodeInfo);
  118. pbDecode(episodeInfo, Episode_fields, response->parts[0]);
  119. CSPOT_LOG(info, "--- Episode name: %s", episodeInfo.name);
  120. this->fileId = std::vector<uint8_t>();
  121. // TODO: option to set file quality
  122. for (int x = 0; x < episodeInfo.audio_count; x++)
  123. {
  124. if (episodeInfo.audio[x].format == AudioFormat_OGG_VORBIS_96)
  125. {
  126. this->fileId = pbArrayToVector(episodeInfo.audio[x].file_id);
  127. break; // If file found stop searching
  128. }
  129. }
  130. if (trackInfoReceived != nullptr)
  131. {
  132. auto imageId = pbArrayToVector(episodeInfo.covers->image[0].file_id);
  133. TrackInfo simpleTrackInfo = {
  134. .name = std::string(episodeInfo.name),
  135. .album = "",
  136. .artist = "",
  137. .imageUrl = "https://i.scdn.co/image/" + bytesToHexString(imageId),
  138. .duration = trackInfo.duration,
  139. };
  140. trackInfoReceived(simpleTrackInfo);
  141. }
  142. this->requestAudioKey(pbArrayToVector(episodeInfo.gid), this->fileId, episodeInfo.duration, position_ms, isPaused);
  143. }
  144. void SpotifyTrack::requestAudioKey(std::vector<uint8_t> fileId, std::vector<uint8_t> trackId, int32_t trackDuration, uint32_t position_ms, bool isPaused)
  145. {
  146. audioKeyCallback audioKeyLambda = [=](bool success, std::vector<uint8_t> res) {
  147. if (success)
  148. {
  149. CSPOT_LOG(info, "Successfully got audio key!");
  150. auto audioKey = std::vector<uint8_t>(res.begin() + 4, res.end());
  151. if (this->fileId.size() > 0)
  152. {
  153. this->audioStream = std::make_unique<ChunkedAudioStream>(this->fileId, audioKey, trackDuration, this->manager, position_ms, isPaused);
  154. loadedTrackCallback();
  155. }
  156. else
  157. {
  158. CSPOT_LOG(error, "Error while fetching audiokey...");
  159. }
  160. }
  161. else
  162. {
  163. auto code = ntohs(extract<uint16_t>(res, 4));
  164. CSPOT_LOG(error, "Error while fetching audiokey, error code: %d", code);
  165. }
  166. };
  167. this->manager->requestAudioKey(trackId, fileId, audioKeyLambda);
  168. }