123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319 |
- #include "SpircHandler.h"
- #include <memory>
- #include "AccessKeyFetcher.h"
- #include "BellUtils.h"
- #include "CSpotContext.h"
- #include "Logger.h"
- #include "MercurySession.h"
- #include "PlaybackState.h"
- #include "TrackPlayer.h"
- #include "TrackReference.h"
- #include "protobuf/spirc.pb.h"
- using namespace cspot;
- SpircHandler::SpircHandler(std::shared_ptr<cspot::Context> ctx)
- : playbackState(ctx) {
- auto isAiringCallback = [this]() {
- return !(isNextTrackPreloaded || isRequestedFromLoad);
- };
- auto EOFCallback = [this]() {
- auto ref = this->playbackState.getNextTrackRef();
- if (!isNextTrackPreloaded && !isRequestedFromLoad && ref != nullptr) {
- isNextTrackPreloaded = true;
- auto trackRef = TrackReference::fromTrackRef(ref);
- this->trackPlayer->loadTrackFromRef(trackRef, 0, true);
- }
-
- if (ref == nullptr) {
- sendEvent(EventType::DEPLETED);
- }
- };
- auto trackLoadedCallback = [this]() {
- this->currentTrackInfo = this->trackPlayer->getCurrentTrackInfo();
- if (isRequestedFromLoad) {
- sendEvent(EventType::PLAYBACK_START, (int)nextTrackPosition);
- setPause(false);
- }
- };
- this->ctx = ctx;
- this->trackPlayer = std::make_shared<TrackPlayer>(ctx, isAiringCallback, EOFCallback, trackLoadedCallback);
- // Subscribe to mercury on session ready
- ctx->session->setConnectedHandler([this]() { this->subscribeToMercury(); });
- }
- void SpircHandler::subscribeToMercury() {
- auto responseLambda = [this](MercurySession::Response& res) {
- if (res.fail)
- return;
- sendCmd(MessageType_kMessageTypeHello);
- CSPOT_LOG(debug, "Sent kMessageTypeHello!");
- // Assign country code
- this->ctx->config.countryCode = this->ctx->session->getCountryCode();
- };
- auto subscriptionLambda = [this](MercurySession::Response& res) {
- if (res.fail)
- return;
- CSPOT_LOG(debug, "Received subscription response");
- this->handleFrame(res.parts[0]);
- };
- ctx->session->executeSubscription(
- MercurySession::RequestType::SUB,
- "hm://remote/user/" + ctx->config.username + "/", responseLambda,
- subscriptionLambda);
- }
- void SpircHandler::loadTrackFromURI(const std::string& uri) {
- // {track/episode}:{gid}
- bool isEpisode = uri.find("episode:") != std::string::npos;
- auto gid = stringHexToBytes(uri.substr(uri.find(":") + 1));
- auto trackRef = TrackReference::fromGID(gid, isEpisode);
- isRequestedFromLoad = true;
- isNextTrackPreloaded = false;
- playbackState.setActive(true);
- auto playbackRef = playbackState.getCurrentTrackRef();
- if (playbackRef != nullptr) {
- playbackState.updatePositionMs(playbackState.remoteFrame.state.position_ms);
- auto ref = TrackReference::fromTrackRef(playbackRef);
- this->trackPlayer->loadTrackFromRef(
- ref, playbackState.remoteFrame.state.position_ms, true);
- playbackState.setPlaybackState(PlaybackState::State::Loading);
- this->nextTrackPosition = playbackState.remoteFrame.state.position_ms;
- }
- this->notify();
- }
- void SpircHandler::notifyAudioReachedPlayback() {
- if (isRequestedFromLoad || isNextTrackPreloaded) {
- playbackState.updatePositionMs(nextTrackPosition);
- playbackState.setPlaybackState(PlaybackState::State::Playing);
- } else {
- setPause(true);
- }
- isRequestedFromLoad = false;
- if (isNextTrackPreloaded) {
- isNextTrackPreloaded = false;
- playbackState.nextTrack();
- nextTrackPosition = 0;
- }
- this->notify();
- sendEvent(EventType::TRACK_INFO, this->trackPlayer->getCurrentTrackInfo());
- }
- void SpircHandler::updatePositionMs(uint32_t position) {
- playbackState.updatePositionMs(position);
- notify();
- }
- void SpircHandler::disconnect() {
- this->trackPlayer->stopTrack();
- this->ctx->session->disconnect();
- }
- void SpircHandler::handleFrame(std::vector<uint8_t>& data) {
- pb_release(Frame_fields, &playbackState.remoteFrame);
- pbDecode(playbackState.remoteFrame, Frame_fields, data);
- switch (playbackState.remoteFrame.typ) {
- case MessageType_kMessageTypeNotify: {
- CSPOT_LOG(debug, "Notify frame");
- // Pause the playback if another player took control
- if (playbackState.isActive() &&
- playbackState.remoteFrame.device_state.is_active) {
- CSPOT_LOG(debug, "Another player took control, pausing playback");
- playbackState.setActive(false);
- this->trackPlayer->stopTrack();
- sendEvent(EventType::DISC);
- }
- break;
- }
- case MessageType_kMessageTypeSeek: {
- /* If next track is already downloading, we can't seek in the current one anymore. Also,
- * when last track has been reached, we has to restart as we can't tell the difference */
- if ((!isNextTrackPreloaded && this->playbackState.getNextTrackRef()) || isRequestedFromLoad) {
- CSPOT_LOG(debug, "Seek command while streaming current");
- sendEvent(EventType::SEEK, (int)playbackState.remoteFrame.position);
- playbackState.updatePositionMs(playbackState.remoteFrame.position);
- trackPlayer->seekMs(playbackState.remoteFrame.position);
- } else {
- CSPOT_LOG(debug, "Seek command while streaming next or before started");
- isRequestedFromLoad = true;
- isNextTrackPreloaded = false;
- auto ref = TrackReference::fromTrackRef(playbackState.getCurrentTrackRef());
- this->trackPlayer->loadTrackFromRef(ref, playbackState.remoteFrame.position, true);
- this->nextTrackPosition = playbackState.remoteFrame.position;
- }
- notify();
- break;
- }
- case MessageType_kMessageTypeVolume:
- playbackState.setVolume(playbackState.remoteFrame.volume);
- this->notify();
- sendEvent(EventType::VOLUME, (int)playbackState.remoteFrame.volume);
- break;
- case MessageType_kMessageTypePause:
- setPause(true);
- break;
- case MessageType_kMessageTypePlay:
- setPause(false);
- break;
- case MessageType_kMessageTypeNext:
- nextSong();
- sendEvent(EventType::NEXT);
- break;
- case MessageType_kMessageTypePrev:
- previousSong();
- sendEvent(EventType::PREV);
- break;
- case MessageType_kMessageTypeLoad: {
- CSPOT_LOG(debug, "Load frame!");
- isRequestedFromLoad = true;
- isNextTrackPreloaded = false;
- playbackState.setActive(true);
- playbackState.updateTracks();
- auto playbackRef = playbackState.getCurrentTrackRef();
- if (playbackRef != nullptr) {
- playbackState.updatePositionMs(
- playbackState.remoteFrame.state.position_ms);
- auto ref = TrackReference::fromTrackRef(playbackRef);
- this->trackPlayer->loadTrackFromRef(
- ref, playbackState.remoteFrame.state.position_ms, true);
- playbackState.setPlaybackState(PlaybackState::State::Loading);
- this->nextTrackPosition = playbackState.remoteFrame.state.position_ms;
- }
- this->notify();
- break;
- }
- case MessageType_kMessageTypeReplace: {
- CSPOT_LOG(debug, "Got replace frame");
- playbackState.updateTracks();
- this->notify();
- break;
- }
- case MessageType_kMessageTypeShuffle: {
- CSPOT_LOG(debug, "Got shuffle frame");
- playbackState.setShuffle(playbackState.remoteFrame.state.shuffle);
- this->notify();
- break;
- }
- case MessageType_kMessageTypeRepeat: {
- CSPOT_LOG(debug, "Got repeat frame");
- playbackState.setRepeat(playbackState.remoteFrame.state.repeat);
- this->notify();
- break;
- }
- default:
- break;
- }
- }
- void SpircHandler::setRemoteVolume(int volume) {
- playbackState.setVolume(volume);
- notify();
- }
- void SpircHandler::notify() {
- this->sendCmd(MessageType_kMessageTypeNotify);
- }
- void SpircHandler::nextSong() {
- if (playbackState.nextTrack()) {
- isRequestedFromLoad = true;
- isNextTrackPreloaded = false;
- auto ref = TrackReference::fromTrackRef(playbackState.getCurrentTrackRef());
- this->trackPlayer->loadTrackFromRef(ref, 0, true);
- } else {
- sendEvent(EventType::FLUSH);
- playbackState.updatePositionMs(0);
- trackPlayer->stopTrack();
- }
- this->nextTrackPosition = 0;
- notify();
- }
- void SpircHandler::previousSong() {
- playbackState.prevTrack();
- isRequestedFromLoad = true;
- isNextTrackPreloaded = false;
- sendEvent(EventType::PREV);
- auto ref = TrackReference::fromTrackRef(playbackState.getCurrentTrackRef());
- this->trackPlayer->loadTrackFromRef(ref, 0, true);
- this->nextTrackPosition = 0;
- notify();
- }
- std::shared_ptr<TrackPlayer> SpircHandler::getTrackPlayer() {
- return this->trackPlayer;
- }
- void SpircHandler::sendCmd(MessageType typ) {
- // Serialize current player state
- auto encodedFrame = playbackState.encodeCurrentFrame(typ);
- auto responseLambda = [=](MercurySession::Response& res) {
- };
- auto parts = MercurySession::DataParts({encodedFrame});
- ctx->session->execute(MercurySession::RequestType::SEND,
- "hm://remote/user/" + ctx->config.username + "/",
- responseLambda, parts);
- }
- void SpircHandler::setEventHandler(EventHandler handler) {
- this->eventHandler = handler;
- }
- void SpircHandler::setPause(bool isPaused) {
- if (isPaused) {
- CSPOT_LOG(debug, "External pause command");
- playbackState.setPlaybackState(PlaybackState::State::Paused);
- } else {
- CSPOT_LOG(debug, "External play command");
- playbackState.setPlaybackState(PlaybackState::State::Playing);
- }
- notify();
- sendEvent(EventType::PLAY_PAUSE, isPaused);
- }
- void SpircHandler::sendEvent(EventType type) {
- auto event = std::make_unique<Event>();
- event->eventType = type;
- event->data = {};
- eventHandler(std::move(event));
- }
- void SpircHandler::sendEvent(EventType type, EventData data) {
- auto event = std::make_unique<Event>();
- event->eventType = type;
- event->data = data;
- eventHandler(std::move(event));
- }
|