Browse Source

cspot fixes backport

philippe44 1 year ago
parent
commit
ca38a14420
3 changed files with 28 additions and 14 deletions
  1. 4 2
      README.md
  2. 17 10
      components/spotify/Shim.cpp
  3. 7 2
      components/spotify/cspot/src/SpircHandler.cpp

+ 4 - 2
README.md

@@ -187,7 +187,7 @@ bck=<gpio>,ws=<gpio>,do=<gpio>[,mck=0|1|2][,mute=<gpio>[:0|1][,model=TAS57xx|TAS
 ```
 if "model" is not set or is not recognized, then default "I2S" is used. The option "mck" is used for some codecs that require a master clock (although they should not). By default GPIO0 is used as MCLK and only recent builds (post mid-2023) can use 1 or 2. Also be aware that this cannot coexit with RMII Ethernet (see ethernet section below). I2C parameters are optional and only needed if your DAC requires an I2C control (See 'dac_controlset' below). Note that "i2c" parameters are decimal, hex notation is not allowed.
 
-So far, TAS57xx, TAS5713, AC101, WM8978 and ES8388 are recognized models where the proper init sequence/volume/power controls are sent. For other codecs that might require an I2C commands, please use the parameter "dac_controlset" that allows definition of simple commands to be sent over i2c for init, power, speakder and headset on and off using a JSON syntax:
+So far, TAS57xx, TAS5713, AC101, WM8978 and ES8388 are recognized models where the proper init sequence/volume/power controls are sent. For other codecs that might require an I2C commands, please use the parameter "dac_controlset" that allows definition of simple commands to be sent over i2c for init, power, speaker and headset on and off using a JSON syntax:
 ```json
 { <command>: [ {"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"}, ... {{"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"} ],
   <command>: [ {"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"}, ... {{"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"} ],
@@ -197,6 +197,8 @@ Where `<command>` is one of init, poweron, poweroff, speakeron, speakeroff, head
 
 This is standard JSON notation, so if you are not familiar with it, Google is your best friend. Be aware that the '...' means you can have as many entries as you want, it's not part of the syntax. Every section is optional, but it does not make sense to set i2c in the 'dac_config' parameter and not setting anything here. The parameter 'mode' allows to *or* the register with the value or to *and* it. Don't set 'mode' if you simply want to write. The 'val parameter can be an array [v1, v2,...] to write a serie of bytes in a single i2c burst (in that case 'mode' is ignored). **Note that all values must be decimal**. You can use a validator like [this](https://jsonlint.com) to verify your syntax
 
+The 'power' command is used when powering on/off the DAC after the idle period (see -C option of squeezelite) and the 'speaker/headset' commands are sent when switching between speakers and headsets (see headset jack detection).
+
 NB: For named configurations ((SqueezeAMP, Muse ... all except I2S), all this is ignored. For know codecs, the built-in sequences can be overwritten using dac_controlset
 
 **Please note that you can not use the same GPIO or port as the I2C.**
@@ -275,7 +277,7 @@ GPIO can be set to GND provide or Vcc at boot. This is convenient to power devic
 
 The `<amp>` parameter can use used to assign a GPIO that will be set to active level (default 1) when playback starts. It will be reset when squeezelite becomes idle. The idle timeout is set on the squeezelite command line through `-C <timeout>`
 
-The `<power>` parameter can use used to assign a GPIO that will be set to active level (default 1) when player is powered on and reset when powered off
+The `<power>` parameter can use used to assign a GPIO that will be set to active level (default 1) when player is powered on and reset when powered off (in LMS, does not apply to AirPlay, Spotify or BT).
 
 If you have an audio jack that supports insertion (use :0 or :1 to set the level when inserted), you can specify which GPIO it's connected to. Using the parameter jack_mutes_amp allows to mute the amp when headset (e.g.) is inserted.
 

+ 17 - 10
components/spotify/Shim.cpp

@@ -53,6 +53,7 @@ private:
     std::atomic<states> state;
     std::string credentials;
     bool zeroConf;
+    std::atomic<bool> flushed = false, notify = true;
         
     int startOffset, volume = 0, bitrate = 160;
     httpd_handle_t serverHandle;
@@ -60,6 +61,7 @@ private:
     cspot_cmd_cb_t cmdHandler;
     cspot_data_cb_t dataHandler;
     std::string lastTrackId;
+    cspot::TrackInfo trackInfo;
 
     std::shared_ptr<cspot::LoginBlob> blob;
     std::unique_ptr<cspot::SpircHandler> spirc;
@@ -206,11 +208,13 @@ void cspotPlayer::eventHandler(std::unique_ptr<cspot::SpircHandler::Event> event
         trackStatus = TRACK_INIT;
         // memorize position for when track's beginning will be detected
         startOffset = std::get<int>(event->data);
+        notify = !flushed;
+        flushed = false;
         // Spotify servers do not send volume at connection
         spirc->setRemoteVolume(volume);
 
         cmdHandler(CSPOT_START, 44100);
-        CSPOT_LOG(info, "(re)start playing");
+        CSPOT_LOG(info, "(re)start playing at %d", startOffset);
         break;
     }
     case cspot::SpircHandler::EventType::PLAY_PAUSE: {
@@ -219,16 +223,14 @@ void cspotPlayer::eventHandler(std::unique_ptr<cspot::SpircHandler::Event> event
         break;
     }
     case cspot::SpircHandler::EventType::TRACK_INFO: {
-        auto trackInfo = std::get<cspot::TrackInfo>(event->data);
-        cmdHandler(CSPOT_TRACK_INFO, trackInfo.duration, startOffset, trackInfo.artist.c_str(),
-                       trackInfo.album.c_str(), trackInfo.name.c_str(), trackInfo.imageUrl.c_str());
-        spirc->updatePositionMs(startOffset);
-        startOffset = 0;
+        trackInfo = std::get<cspot::TrackInfo>(event->data);
         break;
     }
+    case cspot::SpircHandler::EventType::FLUSH: 
+        flushed = true;
+        __attribute__ ((fallthrough));        
     case cspot::SpircHandler::EventType::NEXT:
-    case cspot::SpircHandler::EventType::PREV:
-    case cspot::SpircHandler::EventType::FLUSH: {
+    case cspot::SpircHandler::EventType::PREV: {
         cmdHandler(CSPOT_FLUSH);
         break;
     }
@@ -411,8 +413,13 @@ void cspotPlayer::runTask() {
                     uint32_t started;
                     cmdHandler(CSPOT_QUERY_STARTED, &started);
                     if (started) {
-                        CSPOT_LOG(info, "next track's audio has reached DAC");
-                        spirc->notifyAudioReachedPlayback();
+                        CSPOT_LOG(info, "next track's audio has reached DAC (offset %d)", startOffset);
+                        if (notify) spirc->notifyAudioReachedPlayback();
+                        else notify = true;
+                        cmdHandler(CSPOT_TRACK_INFO, trackInfo.duration, startOffset, trackInfo.artist.c_str(),
+                                    trackInfo.album.c_str(), trackInfo.name.c_str(), trackInfo.imageUrl.c_str());
+                        spirc->updatePositionMs(startOffset);
+                        startOffset = 0;
                         trackStatus = TRACK_STREAM;
                     }
                 } else if (trackStatus == TRACK_END) {

+ 7 - 2
components/spotify/cspot/src/SpircHandler.cpp

@@ -204,8 +204,13 @@ void SpircHandler::handleFrame(std::vector<uint8_t>& data) {
       CSPOT_LOG(debug, "Got replace frame");
       playbackState->syncWithRemote();
 
-      trackQueue->updateTracks(playbackState->remoteFrame.state.position_ms,
-                               false);
+      // 1st track is the current one, but update the position
+      trackQueue->updateTracks(
+          playbackState->remoteFrame.state.position_ms +
+              ctx->timeProvider->getSyncedTimestamp() -
+              playbackState->innerFrame.state.position_measured_at,
+          false);
+
       this->notify();
 
       sendEvent(EventType::FLUSH);