#include "SpotifyTrack.h" #include "unistd.h" #include "MercuryManager.h" #include #include "CspotAssert.h" #include "Logger.h" #include "ConfigJSON.h" SpotifyTrack::SpotifyTrack(std::shared_ptr manager, std::shared_ptr trackReference, uint32_t position_ms, bool isPaused) { this->manager = manager; this->fileId = std::vector(); episodeInfo = Episode_init_default; trackInfo = Track_init_default; mercuryCallback trackResponseLambda = [=](std::unique_ptr res) { this->trackInformationCallback(std::move(res), position_ms, isPaused); }; mercuryCallback episodeResponseLambda = [=](std::unique_ptr res) { this->episodeInformationCallback(std::move(res), position_ms, isPaused); }; if (trackReference->isEpisode) { this->reqSeqNum = this->manager->execute(MercuryType::GET, "hm://metadata/3/episode/" + bytesToHexString(trackReference->gid), episodeResponseLambda); } else { this->reqSeqNum = this->manager->execute(MercuryType::GET, "hm://metadata/3/track/" + bytesToHexString(trackReference->gid), trackResponseLambda); } } SpotifyTrack::~SpotifyTrack() { this->manager->unregisterMercuryCallback(this->reqSeqNum); this->manager->freeAudioKeyCallback(); pb_release(Track_fields, this->trackInfo); pb_release(Episode_fields, this->episodeInfo); } bool SpotifyTrack::countryListContains(std::string countryList, std::string country) { for (int x = 0; x < countryList.size(); x += 2) { if (countryList.substr(x, 2) == country) { return true; } } return false; } bool SpotifyTrack::canPlayTrack() { for (int x = 0; x < trackInfo.restriction_count; x++) { if (trackInfo.restriction[x].countries_allowed != nullptr) { return countryListContains(std::string(trackInfo.restriction[x].countries_allowed), manager->countryCode); } if (trackInfo.restriction[x].countries_forbidden != nullptr) { return !countryListContains(std::string(trackInfo.restriction[x].countries_forbidden), manager->countryCode); } } return true; } void SpotifyTrack::trackInformationCallback(std::unique_ptr response, uint32_t position_ms, bool isPaused) { if (this->fileId.size() != 0) return; CSPOT_ASSERT(response->parts.size() > 0, "response->parts.size() must be greater than 0"); pb_release(Track_fields, trackInfo); pbDecode(trackInfo, Track_fields, response->parts[0]); CSPOT_LOG(info, "Track name: %s", trackInfo.name); CSPOT_LOG(info, "Track duration: %d", trackInfo.duration); CSPOT_LOG(debug, "trackInfo.restriction.size() = %d", trackInfo.restriction_count); int altIndex = 0; while (!canPlayTrack()) { trackInfo.restriction = trackInfo.alternative[altIndex].restriction; trackInfo.restriction_count = trackInfo.alternative[altIndex].restriction_count; trackInfo.gid = trackInfo.alternative[altIndex].gid; trackInfo.file = trackInfo.alternative[altIndex].file; altIndex++; CSPOT_LOG(info, "Trying alternative %d", altIndex); } auto trackId = pbArrayToVector(trackInfo.gid); this->fileId = std::vector(); for (int x = 0; x < trackInfo.file_count; x++) { if (trackInfo.file[x].format == configMan->format) { this->fileId = pbArrayToVector(trackInfo.file[x].file_id); break; // If file found stop searching } } if (trackInfoReceived != nullptr) { auto imageId = pbArrayToVector(trackInfo.album.cover_group.image[0].file_id); TrackInfo simpleTrackInfo = { .name = std::string(trackInfo.name), .album = std::string(trackInfo.album.name), .artist = std::string(trackInfo.artist[0].name), .imageUrl = "https://i.scdn.co/image/" + bytesToHexString(imageId), .duration = trackInfo.duration, }; trackInfoReceived(simpleTrackInfo); } this->requestAudioKey(this->fileId, trackId, trackInfo.duration, position_ms, isPaused); } void SpotifyTrack::episodeInformationCallback(std::unique_ptr response, uint32_t position_ms, bool isPaused) { if (this->fileId.size() != 0) return; CSPOT_LOG(debug, "Got to episode"); CSPOT_ASSERT(response->parts.size() > 0, "response->parts.size() must be greater than 0"); pb_release(Episode_fields, episodeInfo); pbDecode(episodeInfo, Episode_fields, response->parts[0]); CSPOT_LOG(info, "--- Episode name: %s", episodeInfo.name); this->fileId = std::vector(); // TODO: option to set file quality for (int x = 0; x < episodeInfo.audio_count; x++) { if (episodeInfo.audio[x].format == AudioFormat_OGG_VORBIS_96) { this->fileId = pbArrayToVector(episodeInfo.audio[x].file_id); break; // If file found stop searching } } if (trackInfoReceived != nullptr) { auto imageId = pbArrayToVector(episodeInfo.covers->image[0].file_id); TrackInfo simpleTrackInfo = { .name = std::string(episodeInfo.name), .album = "", .artist = "", .imageUrl = "https://i.scdn.co/image/" + bytesToHexString(imageId), .duration = trackInfo.duration, }; trackInfoReceived(simpleTrackInfo); } this->requestAudioKey(pbArrayToVector(episodeInfo.gid), this->fileId, episodeInfo.duration, position_ms, isPaused); } void SpotifyTrack::requestAudioKey(std::vector fileId, std::vector trackId, int32_t trackDuration, uint32_t position_ms, bool isPaused) { audioKeyCallback audioKeyLambda = [=](bool success, std::vector res) { if (success) { CSPOT_LOG(info, "Successfully got audio key!"); auto audioKey = std::vector(res.begin() + 4, res.end()); if (this->fileId.size() > 0) { this->audioStream = std::make_unique(this->fileId, audioKey, trackDuration, this->manager, position_ms, isPaused); loadedTrackCallback(); } else { CSPOT_LOG(error, "Error while fetching audiokey..."); } } else { auto code = ntohs(extract(res, 4)); CSPOT_LOG(error, "Error while fetching audiokey, error code: %d", code); } }; this->manager->requestAudioKey(trackId, fileId, audioKeyLambda); }