SpotifyTrack.cpp 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  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. mercuryCallback trackResponseLambda = [=](std::unique_ptr<MercuryResponse> res) {
  13. this->trackInformationCallback(std::move(res), position_ms, isPaused);
  14. };
  15. mercuryCallback episodeResponseLambda = [=](std::unique_ptr<MercuryResponse> res) {
  16. this->episodeInformationCallback(std::move(res), position_ms, isPaused);
  17. };
  18. if (trackReference->isEpisode)
  19. {
  20. this->reqSeqNum = this->manager->execute(MercuryType::GET, "hm://metadata/3/episode/" + bytesToHexString(trackReference->gid), episodeResponseLambda);
  21. }
  22. else
  23. {
  24. this->reqSeqNum = this->manager->execute(MercuryType::GET, "hm://metadata/3/track/" + bytesToHexString(trackReference->gid), trackResponseLambda);
  25. }
  26. }
  27. SpotifyTrack::~SpotifyTrack()
  28. {
  29. this->manager->unregisterMercuryCallback(this->reqSeqNum);
  30. this->manager->freeAudioKeyCallback();
  31. }
  32. bool SpotifyTrack::countryListContains(std::string countryList, std::string country)
  33. {
  34. for (int x = 0; x < countryList.size(); x += 2)
  35. {
  36. if (countryList.substr(x, 2) == country)
  37. {
  38. return true;
  39. }
  40. }
  41. return false;
  42. }
  43. bool SpotifyTrack::canPlayTrack(std::vector<Restriction>& restrictions)
  44. {
  45. for (int x = 0; x < restrictions.size(); x++)
  46. {
  47. if (restrictions[x].countries_allowed.has_value())
  48. {
  49. return countryListContains(restrictions[x].countries_allowed.value(), manager->countryCode);
  50. }
  51. if (restrictions[x].countries_forbidden.has_value())
  52. {
  53. return !countryListContains(restrictions[x].countries_forbidden.value(), manager->countryCode);
  54. }
  55. }
  56. return true;
  57. }
  58. void SpotifyTrack::trackInformationCallback(std::unique_ptr<MercuryResponse> response, uint32_t position_ms, bool isPaused)
  59. {
  60. if (this->fileId.size() != 0)
  61. return;
  62. CSPOT_ASSERT(response->parts.size() > 0, "response->parts.size() must be greater than 0");
  63. trackInfo = decodePb<Track>(response->parts[0]);
  64. CSPOT_LOG(info, "Track name: %s", trackInfo.name.value().c_str());
  65. CSPOT_LOG(debug, "trackInfo.restriction.size() = %d", trackInfo.restriction.size());
  66. int altIndex = 0;
  67. while (!canPlayTrack(trackInfo.restriction))
  68. {
  69. trackInfo.restriction = trackInfo.alternative[altIndex].restriction;
  70. trackInfo.gid = trackInfo.alternative[altIndex].gid;
  71. trackInfo.file = trackInfo.alternative[altIndex].file;
  72. altIndex++;
  73. CSPOT_LOG(info, "Trying alternative %d", altIndex);
  74. }
  75. auto trackId = trackInfo.gid.value();
  76. this->fileId = std::vector<uint8_t>();
  77. for (int x = 0; x < trackInfo.file.size(); x++)
  78. {
  79. if (trackInfo.file[x].format == configMan->format)
  80. {
  81. this->fileId = trackInfo.file[x].file_id.value();
  82. break; // If file found stop searching
  83. }
  84. }
  85. if (trackInfoReceived != nullptr)
  86. {
  87. TrackInfo simpleTrackInfo = {
  88. .name = trackInfo.name.value(),
  89. .album = trackInfo.album.value().name.value(),
  90. .artist = trackInfo.artist[0].name.value(),
  91. .imageUrl = "https://i.scdn.co/image/" + bytesToHexString(trackInfo.album.value().cover_group.value().image[0].file_id.value()),
  92. .duration = trackInfo.duration.value(),
  93. };
  94. trackInfoReceived(simpleTrackInfo);
  95. }
  96. this->requestAudioKey(this->fileId, trackId, trackInfo.duration.value(), position_ms, isPaused);
  97. }
  98. void SpotifyTrack::episodeInformationCallback(std::unique_ptr<MercuryResponse> response, uint32_t position_ms, bool isPaused)
  99. {
  100. if (this->fileId.size() != 0)
  101. return;
  102. CSPOT_LOG(debug, "Got to episode");
  103. CSPOT_ASSERT(response->parts.size() > 0, "response->parts.size() must be greater than 0");
  104. episodeInfo = decodePb<Episode>(response->parts[0]);
  105. CSPOT_LOG(info, "--- Episode name: %s", episodeInfo.name.value().c_str());
  106. this->fileId = std::vector<uint8_t>();
  107. // TODO: option to set file quality
  108. for (int x = 0; x < episodeInfo.audio.size(); x++)
  109. {
  110. if (episodeInfo.audio[x].format == AudioFormat::OGG_VORBIS_96)
  111. {
  112. this->fileId = episodeInfo.audio[x].file_id.value();
  113. break; // If file found stop searching
  114. }
  115. }
  116. this->requestAudioKey(episodeInfo.gid.value(), this->fileId, episodeInfo.duration.value(), position_ms, isPaused);
  117. }
  118. void SpotifyTrack::requestAudioKey(std::vector<uint8_t> fileId, std::vector<uint8_t> trackId, int32_t trackDuration, uint32_t position_ms, bool isPaused)
  119. {
  120. audioKeyCallback audioKeyLambda = [=](bool success, std::vector<uint8_t> res) {
  121. if (success)
  122. {
  123. CSPOT_LOG(info, "Successfully got audio key!");
  124. auto audioKey = std::vector<uint8_t>(res.begin() + 4, res.end());
  125. if (this->fileId.size() > 0)
  126. {
  127. this->audioStream = std::make_unique<ChunkedAudioStream>(this->fileId, audioKey, trackDuration, this->manager, position_ms, isPaused);
  128. loadedTrackCallback();
  129. }
  130. else
  131. {
  132. CSPOT_LOG(error, "Error while fetching audiokey...");
  133. }
  134. }
  135. else
  136. {
  137. auto code = ntohs(extract<uint16_t>(res, 4));
  138. CSPOT_LOG(error, "Error while fetching audiokey, error code: %d", code);
  139. }
  140. };
  141. this->manager->requestAudioKey(trackId, fileId, audioKeyLambda);
  142. }