SpircHandler.cpp 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. #include "SpircHandler.h"
  2. #include <memory>
  3. #include "AccessKeyFetcher.h"
  4. #include "BellUtils.h"
  5. #include "CSpotContext.h"
  6. #include "Logger.h"
  7. #include "MercurySession.h"
  8. #include "PlaybackState.h"
  9. #include "TrackPlayer.h"
  10. #include "TrackReference.h"
  11. #include "protobuf/spirc.pb.h"
  12. using namespace cspot;
  13. SpircHandler::SpircHandler(std::shared_ptr<cspot::Context> ctx)
  14. : playbackState(ctx) {
  15. auto isAiringCallback = [this]() {
  16. return !(isNextTrackPreloaded || isRequestedFromLoad);
  17. };
  18. auto EOFCallback = [this]() {
  19. auto ref = this->playbackState.getNextTrackRef();
  20. if (!isNextTrackPreloaded && !isRequestedFromLoad && ref != nullptr) {
  21. isNextTrackPreloaded = true;
  22. auto trackRef = TrackReference::fromTrackRef(ref);
  23. this->trackPlayer->loadTrackFromRef(trackRef, 0, true);
  24. }
  25. if (ref == nullptr) {
  26. sendEvent(EventType::DEPLETED);
  27. }
  28. };
  29. auto trackLoadedCallback = [this]() {
  30. this->currentTrackInfo = this->trackPlayer->getCurrentTrackInfo();
  31. if (isRequestedFromLoad) {
  32. sendEvent(EventType::PLAYBACK_START, (int)nextTrackPosition);
  33. setPause(false);
  34. }
  35. };
  36. this->ctx = ctx;
  37. this->trackPlayer = std::make_shared<TrackPlayer>(ctx, isAiringCallback, EOFCallback, trackLoadedCallback);
  38. // Subscribe to mercury on session ready
  39. ctx->session->setConnectedHandler([this]() { this->subscribeToMercury(); });
  40. }
  41. void SpircHandler::subscribeToMercury() {
  42. auto responseLambda = [this](MercurySession::Response& res) {
  43. if (res.fail)
  44. return;
  45. sendCmd(MessageType_kMessageTypeHello);
  46. CSPOT_LOG(debug, "Sent kMessageTypeHello!");
  47. // Assign country code
  48. this->ctx->config.countryCode = this->ctx->session->getCountryCode();
  49. };
  50. auto subscriptionLambda = [this](MercurySession::Response& res) {
  51. if (res.fail)
  52. return;
  53. CSPOT_LOG(debug, "Received subscription response");
  54. this->handleFrame(res.parts[0]);
  55. };
  56. ctx->session->executeSubscription(
  57. MercurySession::RequestType::SUB,
  58. "hm://remote/user/" + ctx->config.username + "/", responseLambda,
  59. subscriptionLambda);
  60. }
  61. void SpircHandler::loadTrackFromURI(const std::string& uri) {
  62. // {track/episode}:{gid}
  63. bool isEpisode = uri.find("episode:") != std::string::npos;
  64. auto gid = stringHexToBytes(uri.substr(uri.find(":") + 1));
  65. auto trackRef = TrackReference::fromGID(gid, isEpisode);
  66. isRequestedFromLoad = true;
  67. isNextTrackPreloaded = false;
  68. playbackState.setActive(true);
  69. auto playbackRef = playbackState.getCurrentTrackRef();
  70. if (playbackRef != nullptr) {
  71. playbackState.updatePositionMs(playbackState.remoteFrame.state.position_ms);
  72. auto ref = TrackReference::fromTrackRef(playbackRef);
  73. this->trackPlayer->loadTrackFromRef(
  74. ref, playbackState.remoteFrame.state.position_ms, true);
  75. playbackState.setPlaybackState(PlaybackState::State::Loading);
  76. this->nextTrackPosition = playbackState.remoteFrame.state.position_ms;
  77. }
  78. this->notify();
  79. }
  80. void SpircHandler::notifyAudioReachedPlayback() {
  81. if (isRequestedFromLoad || isNextTrackPreloaded) {
  82. playbackState.updatePositionMs(nextTrackPosition);
  83. playbackState.setPlaybackState(PlaybackState::State::Playing);
  84. } else {
  85. setPause(true);
  86. }
  87. isRequestedFromLoad = false;
  88. if (isNextTrackPreloaded) {
  89. isNextTrackPreloaded = false;
  90. playbackState.nextTrack();
  91. nextTrackPosition = 0;
  92. }
  93. this->notify();
  94. sendEvent(EventType::TRACK_INFO, this->trackPlayer->getCurrentTrackInfo());
  95. }
  96. void SpircHandler::updatePositionMs(uint32_t position) {
  97. playbackState.updatePositionMs(position);
  98. notify();
  99. }
  100. void SpircHandler::disconnect() {
  101. this->trackPlayer->stopTrack();
  102. this->ctx->session->disconnect();
  103. }
  104. void SpircHandler::handleFrame(std::vector<uint8_t>& data) {
  105. pb_release(Frame_fields, &playbackState.remoteFrame);
  106. pbDecode(playbackState.remoteFrame, Frame_fields, data);
  107. switch (playbackState.remoteFrame.typ) {
  108. case MessageType_kMessageTypeNotify: {
  109. CSPOT_LOG(debug, "Notify frame");
  110. // Pause the playback if another player took control
  111. if (playbackState.isActive() &&
  112. playbackState.remoteFrame.device_state.is_active) {
  113. CSPOT_LOG(debug, "Another player took control, pausing playback");
  114. playbackState.setActive(false);
  115. this->trackPlayer->stopTrack();
  116. sendEvent(EventType::DISC);
  117. }
  118. break;
  119. }
  120. case MessageType_kMessageTypeSeek: {
  121. /* If next track is already downloading, we can't seek in the current one anymore. Also,
  122. * when last track has been reached, we has to restart as we can't tell the difference */
  123. if ((!isNextTrackPreloaded && this->playbackState.getNextTrackRef()) || isRequestedFromLoad) {
  124. CSPOT_LOG(debug, "Seek command while streaming current");
  125. sendEvent(EventType::SEEK, (int)playbackState.remoteFrame.position);
  126. playbackState.updatePositionMs(playbackState.remoteFrame.position);
  127. trackPlayer->seekMs(playbackState.remoteFrame.position);
  128. } else {
  129. CSPOT_LOG(debug, "Seek command while streaming next or before started");
  130. isRequestedFromLoad = true;
  131. isNextTrackPreloaded = false;
  132. auto ref = TrackReference::fromTrackRef(playbackState.getCurrentTrackRef());
  133. this->trackPlayer->loadTrackFromRef(ref, playbackState.remoteFrame.position, true);
  134. this->nextTrackPosition = playbackState.remoteFrame.position;
  135. }
  136. notify();
  137. break;
  138. }
  139. case MessageType_kMessageTypeVolume:
  140. playbackState.setVolume(playbackState.remoteFrame.volume);
  141. this->notify();
  142. sendEvent(EventType::VOLUME, (int)playbackState.remoteFrame.volume);
  143. break;
  144. case MessageType_kMessageTypePause:
  145. setPause(true);
  146. break;
  147. case MessageType_kMessageTypePlay:
  148. setPause(false);
  149. break;
  150. case MessageType_kMessageTypeNext:
  151. nextSong();
  152. sendEvent(EventType::NEXT);
  153. break;
  154. case MessageType_kMessageTypePrev:
  155. previousSong();
  156. sendEvent(EventType::PREV);
  157. break;
  158. case MessageType_kMessageTypeLoad: {
  159. CSPOT_LOG(debug, "Load frame!");
  160. isRequestedFromLoad = true;
  161. isNextTrackPreloaded = false;
  162. playbackState.setActive(true);
  163. playbackState.updateTracks();
  164. auto playbackRef = playbackState.getCurrentTrackRef();
  165. if (playbackRef != nullptr) {
  166. playbackState.updatePositionMs(
  167. playbackState.remoteFrame.state.position_ms);
  168. auto ref = TrackReference::fromTrackRef(playbackRef);
  169. this->trackPlayer->loadTrackFromRef(
  170. ref, playbackState.remoteFrame.state.position_ms, true);
  171. playbackState.setPlaybackState(PlaybackState::State::Loading);
  172. this->nextTrackPosition = playbackState.remoteFrame.state.position_ms;
  173. }
  174. this->notify();
  175. break;
  176. }
  177. case MessageType_kMessageTypeReplace: {
  178. CSPOT_LOG(debug, "Got replace frame");
  179. playbackState.updateTracks();
  180. this->notify();
  181. break;
  182. }
  183. case MessageType_kMessageTypeShuffle: {
  184. CSPOT_LOG(debug, "Got shuffle frame");
  185. playbackState.setShuffle(playbackState.remoteFrame.state.shuffle);
  186. this->notify();
  187. break;
  188. }
  189. case MessageType_kMessageTypeRepeat: {
  190. CSPOT_LOG(debug, "Got repeat frame");
  191. playbackState.setRepeat(playbackState.remoteFrame.state.repeat);
  192. this->notify();
  193. break;
  194. }
  195. default:
  196. break;
  197. }
  198. }
  199. void SpircHandler::setRemoteVolume(int volume) {
  200. playbackState.setVolume(volume);
  201. notify();
  202. }
  203. void SpircHandler::notify() {
  204. this->sendCmd(MessageType_kMessageTypeNotify);
  205. }
  206. void SpircHandler::nextSong() {
  207. if (playbackState.nextTrack()) {
  208. isRequestedFromLoad = true;
  209. isNextTrackPreloaded = false;
  210. auto ref = TrackReference::fromTrackRef(playbackState.getCurrentTrackRef());
  211. this->trackPlayer->loadTrackFromRef(ref, 0, true);
  212. } else {
  213. sendEvent(EventType::FLUSH);
  214. playbackState.updatePositionMs(0);
  215. trackPlayer->stopTrack();
  216. }
  217. this->nextTrackPosition = 0;
  218. notify();
  219. }
  220. void SpircHandler::previousSong() {
  221. playbackState.prevTrack();
  222. isRequestedFromLoad = true;
  223. isNextTrackPreloaded = false;
  224. sendEvent(EventType::PREV);
  225. auto ref = TrackReference::fromTrackRef(playbackState.getCurrentTrackRef());
  226. this->trackPlayer->loadTrackFromRef(ref, 0, true);
  227. this->nextTrackPosition = 0;
  228. notify();
  229. }
  230. std::shared_ptr<TrackPlayer> SpircHandler::getTrackPlayer() {
  231. return this->trackPlayer;
  232. }
  233. void SpircHandler::sendCmd(MessageType typ) {
  234. // Serialize current player state
  235. auto encodedFrame = playbackState.encodeCurrentFrame(typ);
  236. auto responseLambda = [=](MercurySession::Response& res) {
  237. };
  238. auto parts = MercurySession::DataParts({encodedFrame});
  239. ctx->session->execute(MercurySession::RequestType::SEND,
  240. "hm://remote/user/" + ctx->config.username + "/",
  241. responseLambda, parts);
  242. }
  243. void SpircHandler::setEventHandler(EventHandler handler) {
  244. this->eventHandler = handler;
  245. }
  246. void SpircHandler::setPause(bool isPaused) {
  247. if (isPaused) {
  248. CSPOT_LOG(debug, "External pause command");
  249. playbackState.setPlaybackState(PlaybackState::State::Paused);
  250. } else {
  251. CSPOT_LOG(debug, "External play command");
  252. playbackState.setPlaybackState(PlaybackState::State::Playing);
  253. }
  254. notify();
  255. sendEvent(EventType::PLAY_PAUSE, isPaused);
  256. }
  257. void SpircHandler::sendEvent(EventType type) {
  258. auto event = std::make_unique<Event>();
  259. event->eventType = type;
  260. event->data = {};
  261. eventHandler(std::move(event));
  262. }
  263. void SpircHandler::sendEvent(EventType type, EventData data) {
  264. auto event = std::make_unique<Event>();
  265. event->eventType = type;
  266. event->data = data;
  267. eventHandler(std::move(event));
  268. }