diff --git a/libraries/lib-ffmpeg-support/FFmpegFunctions.cpp b/libraries/lib-ffmpeg-support/FFmpegFunctions.cpp
index 9e3edb127f972e97da556f496bee94ef76f0b1ea..66d085a0b7ec534b026e07d1a6d0b3dd5d9fd585 100644
--- a/libraries/lib-ffmpeg-support/FFmpegFunctions.cpp
+++ b/libraries/lib-ffmpeg-support/FFmpegFunctions.cpp
@@ -82,13 +82,13 @@ struct EnvSetter final
    static const wxString VariableName;
    static const wxString Separator;
 
-   EnvSetter()
+   explicit EnvSetter(bool fromUserPathOnly)
    {
       ValueExisted = wxGetEnv(VariableName, &OldValue);
 
       wxString value;
 
-      for (const wxString& path : FFmpegFunctions::GetSearchPaths())
+      for (const wxString& path : FFmpegFunctions::GetSearchPaths(fromUserPathOnly))
       {
          if (!value.empty())
             value += Separator;
@@ -148,7 +148,7 @@ struct FFmpegFunctions::Private final
    AVCodecFactories  CodecFactories;
    AVUtilFactories   UtilFactories;
 
-   std::shared_ptr<wxDynamicLibrary> LibraryWithSymbol(const char* symbol) const
+   std::shared_ptr<wxDynamicLibrary> LibraryWithSymbol(const char* symbol, bool fromUserPathOnly) const
    {
       if (AVFormatLibrary->HasSymbol(symbol))
          return AVFormatLibrary;
@@ -163,21 +163,21 @@ struct FFmpegFunctions::Private final
       if (path.empty())
          return nullptr;
 
-      return LoadLibrary(wxFileNameFromPath(path));
+      return LoadLibrary(wxFileNameFromPath(path), fromUserPathOnly);
    }
 
-   bool Load(FFmpegFunctions& functions, const wxString& path)
+   bool Load(FFmpegFunctions& functions, const wxString& path, bool fromUserPathOnly)
    {
       // We start by loading AVFormat
-      AVFormatLibrary = LoadLibrary(path);
+      AVFormatLibrary = LoadLibrary(path, fromUserPathOnly);
 
       if (AVFormatLibrary == nullptr)
          return false;
 
-      if ((AVCodecLibrary = LibraryWithSymbol("avcodec_version")) == nullptr)
+      if ((AVCodecLibrary = LibraryWithSymbol("avcodec_version", fromUserPathOnly)) == nullptr)
          return false;
 
-      if ((AVUtilLibrary = LibraryWithSymbol("avutil_version")) == nullptr)
+      if ((AVUtilLibrary = LibraryWithSymbol("avutil_version", fromUserPathOnly)) == nullptr)
          return false;
 
       if (
@@ -218,12 +218,12 @@ struct FFmpegFunctions::Private final
       return true;
    }
 
-   std::shared_ptr<wxDynamicLibrary> LoadLibrary(const wxString& libraryName) const
+   std::shared_ptr<wxDynamicLibrary> LoadLibrary(const wxString& libraryName, bool fromUserPathOnly) const
    {
 #if defined(__WXMAC__)
       // On macOS dyld reads environment only when application starts.
       // Let's emulate the process manually
-      for(const wxString& path : FFmpegFunctions::GetSearchPaths())
+      for (const wxString& path : FFmpegFunctions::GetSearchPaths(fromUserPathOnly))
       {
          const wxString fullName = wxFileName(path, libraryName).GetFullPath();
 
@@ -259,7 +259,7 @@ FFmpegFunctions::~FFmpegFunctions()
 {
 }
 
-std::shared_ptr<FFmpegFunctions> FFmpegFunctions::Load()
+std::shared_ptr<FFmpegFunctions> FFmpegFunctions::Load(bool fromUserPathOnly)
 {
    static std::weak_ptr<FFmpegFunctions> weakFunctions;
 
@@ -275,14 +275,14 @@ std::shared_ptr<FFmpegFunctions> FFmpegFunctions::Load()
       FFmpegAPIResolver::Get().GetSuportedAVFormatVersions();
 
 #if !defined(__WXMAC__)
-   EnvSetter envSetter;
+   EnvSetter envSetter(fromUserPathOnly);
 #endif
 
    for (int version : supportedVersions)
    {
       for (const wxString& path : BuildAVFormatPaths(version))
       {
-         if (ffmpeg->mPrivate->Load(*ffmpeg, path))
+         if (ffmpeg->mPrivate->Load(*ffmpeg, path, fromUserPathOnly))
          {
             weakFunctions = ffmpeg;
             return ffmpeg;
@@ -295,14 +295,25 @@ std::shared_ptr<FFmpegFunctions> FFmpegFunctions::Load()
 
 StringSetting AVFormatPath { L"/FFmpeg/FFmpegLibPath", L"" };
 
-std::vector<wxString> FFmpegFunctions::GetSearchPaths()
+std::vector<wxString> FFmpegFunctions::GetSearchPaths(bool fromUserPathOnly)
 {
    std::vector<wxString> paths;
 
    const wxString userAVFormatFullPath = AVFormatPath.Read();
 
    if (!userAVFormatFullPath.empty())
-      paths.emplace_back(wxPathOnly(userAVFormatFullPath));
+   {
+      // For some directories, wxPathOnly will fail.
+      // For example, if path is `c:\ffmpeg-4.4`
+      // wxPathOnly will return `c:\`
+      if (wxDirExists(userAVFormatFullPath))
+         paths.emplace_back(userAVFormatFullPath);
+      else
+         paths.emplace_back(wxPathOnly(userAVFormatFullPath));
+   }
+
+   if (fromUserPathOnly)
+      return paths;
 
 #if defined(__WXMSW__)
    wxRegKey reg(wxT("HKEY_LOCAL_MACHINE\\Software\\FFmpeg for Audacity"));
diff --git a/libraries/lib-ffmpeg-support/FFmpegFunctions.h b/libraries/lib-ffmpeg-support/FFmpegFunctions.h
index be0e59cf040412dc3ce8ce0526bdf0bfc5dea745..371a1dda504285fe350767c6014db1483f658604 100644
--- a/libraries/lib-ffmpeg-support/FFmpegFunctions.h
+++ b/libraries/lib-ffmpeg-support/FFmpegFunctions.h
@@ -86,12 +86,12 @@ struct FFMPEG_SUPPORT_API FFmpegFunctions :
    FFmpegFunctions();
    ~FFmpegFunctions();
 
-   static std::shared_ptr<FFmpegFunctions> Load();
+   static std::shared_ptr<FFmpegFunctions> Load(bool fromUserPathOnly = false);
 
    AVCodecIDFwd (*GetAVCodecID)(AudacityAVCodecID) = nullptr;
    AudacityAVCodecID (*GetAudacityCodecID)(AVCodecIDFwd) = nullptr;
 
-   static std::vector<wxString> GetSearchPaths();
+   static std::vector<wxString> GetSearchPaths(bool fromUserPathOnly);
 
    std::unique_ptr<AVIOContextWrapper> CreateAVIOContext() const;
    std::unique_ptr<AVFormatContextWrapper> CreateAVFormatContext() const;
diff --git a/src/AdornedRulerPanel.cpp b/src/AdornedRulerPanel.cpp
index aa519c562caa6b018708d7f35ce27dfc73b35bba..56359c1aba89910e8477e6730afd94193be17f5e 100644
--- a/src/AdornedRulerPanel.cpp
+++ b/src/AdornedRulerPanel.cpp
@@ -502,14 +502,15 @@ void AdornedRulerPanel::ScrubbingRulerOverlay::Update()
            mNewIndicatorSnapped == -1 && ii < ruler->mNumGuides; ++ii) {
          if (ruler->mIsSnapped[ii]) {
             mNewIndicatorSnapped = ii;
-            mNewQPIndicatorPos = ruler->Time2Pos(ruler->mQuickPlayPos[ii]);
          }
       }
+      mNewQPIndicatorPos = ruler->Time2Pos(
+         ruler->mQuickPlayPos[std::max(0, mNewIndicatorSnapped)]);
 
       // These determine which shape is drawn on the ruler, and whether
       // in the scrub or the qp zone
       mNewScrub =
-         !ruler->Target() && // not doing some other drag in the ruler
+         !ruler->IsMouseCaptured() && // not doing some other drag in the ruler
          (ruler->LastCell() == ruler->mScrubbingCell ||
           (scrubber.HasMark()));
       mNewSeek = mNewScrub &&
@@ -618,13 +619,12 @@ void AdornedRulerPanel::TrackPanelGuidelineOverlay::Draw(
    mOldIndicatorSnapped = mPartner->mNewIndicatorSnapped;
    mOldPreviewingScrub = mNewPreviewingScrub;
 
-   if ( !(mOldPreviewingScrub || mOldIndicatorSnapped >= 0) )
-      return;
-
    if (mOldQPIndicatorPos >= 0) {
       mOldPreviewingScrub
          ? AColor::IndicatorColor(&dc, true) // Draw green line for preview.
-         : AColor::SnapGuidePen(&dc); // Yellow snap guideline
+         : (mOldIndicatorSnapped >= 0)
+            ? AColor::SnapGuidePen(&dc) // Yellow snap guideline
+            : AColor::Light(&dc, false);
 
       // Draw indicator in all visible tracks
       auto pCellularPanel = dynamic_cast<CellularPanel*>( &panel );
diff --git a/src/FFmpeg.cpp b/src/FFmpeg.cpp
index 3503e4035ef82d81f9995e869689c2145b91b54b..07f21124c72de3775c94b10ce4fe81e213d2b4fe 100644
--- a/src/FFmpeg.cpp
+++ b/src/FFmpeg.cpp
@@ -116,7 +116,7 @@ public:
    FindFFmpegDialog(wxWindow *parent, const wxString &path, const wxString &name)
        : wxDialogWrapper(parent, wxID_ANY, XO("Locate FFmpeg"))
        , mName(name)
-       , mFullPath(path, name)
+       , mFullPath(path, {})
    {
       SetName();
 
@@ -183,7 +183,7 @@ public:
 #   if defined(__WXMSW__)
          { XO("Only avformat.dll"), { wxT("avformat-*.dll") } },
 #   elif defined(__WXMAC__)
-         { XO("Only ffmpeg.*.dylib"), { wxT("ffmpeg.*.dylib") } },
+         { XO("Only ffmpeg.*.dylib"), { wxT("ffmpeg.*.dylib"), wxT("libavformat.*.dylib") } },
 #   else
          { XO("Only libavformat.so"), { wxT("libavformat.so.*") } },
 #   endif
@@ -222,7 +222,12 @@ public:
 
    void UpdatePath()
    {
-      mFullPath = mPathText->GetValue();
+      const wxString path = mPathText->GetValue();
+
+      if (wxDirExists(path))
+         mFullPath = wxFileName(path, {}, wxPATH_NATIVE);
+      else
+         mFullPath = mPathText->GetValue();
    }
 
    wxString GetLibPath()
@@ -302,22 +307,53 @@ BEGIN_EVENT_TABLE(FFmpegNotFoundDialog, wxDialogWrapper)
    EVT_BUTTON(wxID_OK, FFmpegNotFoundDialog::OnOk)
 END_EVENT_TABLE()
 
+struct SafeAVFormatPathUpdater final
+{
+   explicit SafeAVFormatPathUpdater(const wxString& newPath)
+   {
+      mHasOldValue = AVFormatPath.Read(&mOldValue);
+
+      AVFormatPath.Write(newPath);
+   }
+
+   ~SafeAVFormatPathUpdater()
+   {
+      if (mRevertToOld)   
+      {
+         if (mHasOldValue)
+            AVFormatPath.Write(mOldValue);
+         else
+            AVFormatPath.Delete();
+      }
+
+      gPrefs->Flush();
+   }
+
+   void CommitValue() noexcept
+   {
+      mRevertToOld = false;
+   }
+
+   wxString mOldValue;
+   bool mHasOldValue;
+   bool mRevertToOld { true };
+};
 
 bool FindFFmpegLibs(wxWindow* parent)
 {
    wxString path;
 
 #if defined(__WXMSW__)
-   const wxString name = wxT("avformat-*.dll");
+   const wxString name = wxT("avformat.dll");
 #elif defined(__WXMAC__)
-   const wxString name = wxT("ffmpeg.*.64bit.dylib");
+   const wxString name = wxT("ffmpeg.64bit.dylib");
 #else
-   const wxString name = wxT("libavformat.so.*");
+   const wxString name = wxT("libavformat.so");
 #endif
 
    wxLogMessage(wxT("Looking for FFmpeg libraries..."));
 
-   auto searchPaths = FFmpegFunctions::GetSearchPaths();
+   auto searchPaths = FFmpegFunctions::GetSearchPaths(false);
 
    if (!searchPaths.empty())
       path = searchPaths.front();
@@ -331,17 +367,25 @@ bool FindFFmpegLibs(wxWindow* parent)
 
    path = fd.GetLibPath();
 
+   const wxFileName fileName(path);
+
+   if (fileName.FileExists())
+      path = fileName.GetPath();
+
    wxLogMessage(wxT("User-specified path = '%s'"), path);
 
-   if (!::wxFileExists(path)) {
-      wxLogError(wxT("User-specified file does not exist. Failed to find FFmpeg libraries."));
+   SafeAVFormatPathUpdater updater(path);
+
+   // Try to load FFmpeg from the user provided path
+   if (!FFmpegFunctions::Load(true))
+   {
+      wxLogError(wxT("User-specified path does not contain FFmpeg libraries."));
       return false;
    }
 
-   wxLogMessage(wxT("User-specified FFmpeg file exists. Success."));
+   updater.CommitValue();
 
-   AVFormatPath.Write(path);
-   gPrefs->Flush();
+   wxLogMessage(wxT("User-specified FFmpeg file exists. Success."));
 
    return true;
 }
diff --git a/src/PlaybackSchedule.cpp b/src/PlaybackSchedule.cpp
index 531b8ec35e3134afa039ae9327777c725f3be90c..cb0601522b461317a4b8042e22e2780b59d85473 100644
--- a/src/PlaybackSchedule.cpp
+++ b/src/PlaybackSchedule.cpp
@@ -14,7 +14,7 @@
 #include "Envelope.h"
 #include "Mix.h"
 #include "Project.h"
-#include "ProjectSettings.h"
+#include "ProjectAudioIO.h"
 #include "SampleCount.h"
 #include "ViewInfo.h" // for PlayRegionEvent
 
@@ -172,7 +172,7 @@ void NewDefaultPlaybackPolicy::Initialize(
    ViewInfo::Get( mProject ).playRegion.Bind( EVT_PLAY_REGION_CHANGE,
       &NewDefaultPlaybackPolicy::OnPlayRegionChange, this);
    if (mVariableSpeed)
-      mProject.Bind( EVT_PROJECT_SETTINGS_CHANGE,
+      mProject.Bind( EVT_PLAY_SPEED_CHANGE,
          &NewDefaultPlaybackPolicy::OnPlaySpeedChange, this);
 }
 
@@ -215,18 +215,18 @@ NewDefaultPlaybackPolicy::GetPlaybackSlice(
 {
    // How many samples to produce for each channel.
    const auto realTimeRemaining = std::max(0.0, schedule.RealTimeRemaining());
-   mRemaining = realTimeRemaining * mRate;
+   mRemaining = realTimeRemaining * mRate / mLastPlaySpeed;
 
-   if (RevertToOldDefault(schedule))
+   if (mLastPlaySpeed == 1.0 && RevertToOldDefault(schedule))
       return PlaybackPolicy::GetPlaybackSlice(schedule, available);
 
    auto frames = available;
    auto toProduce = frames;
-   double deltat = frames / mRate;
+   double deltat = (frames / mRate) * mLastPlaySpeed;
 
    if (deltat > realTimeRemaining)
    {
-      toProduce = frames = mRemaining;
+      toProduce = frames = (realTimeRemaining * mRate) / mLastPlaySpeed;
       schedule.RealTimeAdvance( realTimeRemaining );
    }
    else
@@ -332,7 +332,7 @@ bool NewDefaultPlaybackPolicy::RepositionPlayback(
 
       schedule.RealTimeInit(newTime);
       const auto realTimeRemaining = std::max(0.0, schedule.RealTimeRemaining());
-      mRemaining = realTimeRemaining * mRate;
+      mRemaining = realTimeRemaining * mRate / mLastPlaySpeed;
    }
    else if (speedChange)
       // Don't return early
@@ -397,7 +397,7 @@ void NewDefaultPlaybackPolicy::WriteMessage()
 double NewDefaultPlaybackPolicy::GetPlaySpeed()
 {
    return mVariableSpeed
-      ? ProjectSettings::Get(mProject).GetPlaySpeed()
+      ? ProjectAudioIO::Get(mProject).GetPlaySpeed()
       : 1.0;
 }
 
diff --git a/src/ProjectAudioIO.cpp b/src/ProjectAudioIO.cpp
index ce19f19e3f86a77d1d756008c8981d1f32f5cdcd..b1711d15c10d3dbaf64b7d511c532ea9127ef196 100644
--- a/src/ProjectAudioIO.cpp
+++ b/src/ProjectAudioIO.cpp
@@ -13,6 +13,8 @@ Paul Licameli split from AudacityProject.cpp
 #include "AudioIOBase.h"
 #include "Project.h"
 
+wxDEFINE_EVENT( EVT_PLAY_SPEED_CHANGE, wxCommandEvent);
+
 static const AudacityProject::AttachedObjects::RegisteredFactory sAudioIOKey{
   []( AudacityProject &parent ){
      return std::make_shared< ProjectAudioIO >( parent );
@@ -89,3 +91,12 @@ void ProjectAudioIO::SetCaptureMeter(
       gAudioIO->SetCaptureMeter( project.shared_from_this(), mCaptureMeter );
    }
 }
+
+void ProjectAudioIO::SetPlaySpeed(double value)
+{
+   if (auto oldValue = GetPlaySpeed(); value != oldValue) {
+      mPlaySpeed.store( value, std::memory_order_relaxed );
+      wxCommandEvent evt{ EVT_PLAY_SPEED_CHANGE };
+      mProject.ProcessEvent(evt);
+   }
+}
diff --git a/src/ProjectAudioIO.h b/src/ProjectAudioIO.h
index d8b0c611bd83f3cdf68b4579188bc0c721778a3f..8e46ccf570aa2fe27675804e9dfe3244b0ab1121 100644
--- a/src/ProjectAudioIO.h
+++ b/src/ProjectAudioIO.h
@@ -13,11 +13,17 @@ Paul Licameli split from AudacityProject.h
 
 #include "ClientData.h" // to inherit
 #include <wx/weakref.h>
+#include <wx/event.h> // to declare custom event type
 
+#include <atomic>
 #include <memory>
 class AudacityProject;
 class Meter;
 
+// Sent to the project when the play speed changes
+wxDECLARE_EXPORTED_EVENT(AUDACITY_DLL_API,
+   EVT_PLAY_SPEED_CHANGE, wxCommandEvent);
+
 ///\ brief Holds per-project state needed for interaction with AudioIO,
 /// including the audio stream token and pointers to meters
 class AUDACITY_DLL_API ProjectAudioIO final
@@ -43,6 +49,11 @@ public:
    void SetCaptureMeter(
       const std::shared_ptr<Meter> &capture);
 
+   // Speed play
+   double GetPlaySpeed() const {
+      return mPlaySpeed.load( std::memory_order_relaxed ); }
+   void SetPlaySpeed( double value );
+
 private:
    AudacityProject &mProject;
 
@@ -50,6 +61,10 @@ private:
    std::shared_ptr<Meter> mPlaybackMeter;
    std::shared_ptr<Meter> mCaptureMeter;
 
+   // This is atomic because scrubber may read it in a separate thread from
+   // the main
+   std::atomic<double> mPlaySpeed{};
+
    int  mAudioIOToken{ -1 };
 };
 
diff --git a/src/ProjectSettings.cpp b/src/ProjectSettings.cpp
index 970aa3c2ebc982b5dcb7559d1fd816af4c01ee19..a90b8401a89f6d898fba9563ce4fb6398f635664 100644
--- a/src/ProjectSettings.cpp
+++ b/src/ProjectSettings.cpp
@@ -140,14 +140,6 @@ void ProjectSettings::SetBandwidthSelectionFormatName(
    mBandwidthSelectionFormatName = formatName;
 }
 
-void ProjectSettings::SetPlaySpeed(double value)
-{
-   if (auto oldValue = GetPlaySpeed(); value != oldValue) {
-      mPlaySpeed.store( value, std::memory_order_relaxed );
-      Notify( mProject, ChangedPlaySpeed, oldValue );
-   }
-}
-
 void ProjectSettings::SetSelectionFormat(const NumericFormatSymbol & format)
 {
    mSelectionFormat = format;
diff --git a/src/ProjectSettings.h b/src/ProjectSettings.h
index ecfaff54f064230915b3f1ada5a4226ce62a7f60..858d76ca6f163fe5ada1568cffc5068d17d8ae79 100644
--- a/src/ProjectSettings.h
+++ b/src/ProjectSettings.h
@@ -66,7 +66,6 @@ public:
    enum EventCode : int {
       ChangedSyncLock,
       ChangedTool,
-      ChangedPlaySpeed,
    };
 
    explicit ProjectSettings( AudacityProject &project );
@@ -102,11 +101,6 @@ public:
 
    void SetOvertones(bool isSelected) { mbOvertones = isSelected; }
    bool IsOvertones() const { return mbOvertones; }
-
-   // Speed play
-   double GetPlaySpeed() const {
-      return mPlaySpeed.load( std::memory_order_relaxed ); }
-   void SetPlaySpeed( double value );
    
    // Selection Format
    void SetSelectionFormat(const NumericFormatSymbol & format);
@@ -142,10 +136,6 @@ private:
 
    wxString mSoloPref;
 
-   // This is atomic because scrubber may read it in a separate thread from
-   // the main
-   std::atomic<double> mPlaySpeed{};
-
    int mSnapTo;
 
    int mCurrentTool;
diff --git a/src/toolbars/TranscriptionToolBar.cpp b/src/toolbars/TranscriptionToolBar.cpp
index 73a25334649023d4481fd58ad28248f9aa44752e..376fc44a2e70ffc76b631c7e476fad81ce49117f 100644
--- a/src/toolbars/TranscriptionToolBar.cpp
+++ b/src/toolbars/TranscriptionToolBar.cpp
@@ -37,8 +37,8 @@
 #include "../KeyboardCapture.h"
 #include "NoteTrack.h"
 #include "Project.h"
+#include "../ProjectAudioIO.h"
 #include "../ProjectAudioManager.h"
-#include "../ProjectSettings.h"
 #include "Envelope.h"
 #include "ViewInfo.h"
 #include "../WaveTrack.h"
@@ -157,7 +157,7 @@ void TranscriptionToolBar::Create(wxWindow * parent)
 void TranscriptionToolBar::SetPlaySpeed( double value )
 {
    mPlaySpeed = value;
-   ProjectSettings::Get( mProject ).SetPlaySpeed( GetPlaySpeed() );
+   ProjectAudioIO::Get( mProject ).SetPlaySpeed( GetPlaySpeed() );
 }
 
 /// This is a convenience function that allows for button creation in
diff --git a/src/tracks/ui/Scrubbing.cpp b/src/tracks/ui/Scrubbing.cpp
index 0f33d37d9913b5af464c6813c7ea577d30f50525..657669915b2258dc68c2082479031ee3b381ae1a 100644
--- a/src/tracks/ui/Scrubbing.cpp
+++ b/src/tracks/ui/Scrubbing.cpp
@@ -20,7 +20,6 @@ Paul Licameli split from TrackPanel.cpp
 #include "../../ProjectAudioManager.h"
 #include "../../ProjectHistory.h"
 #include "../../ProjectWindows.h"
-#include "../../ProjectSettings.h"
 #include "ProjectStatus.h"
 #include "../../ScrubState.h"
 #include "Track.h"
@@ -599,8 +598,8 @@ void Scrubber::ContinueScrubbingPoll()
       // default speed of 1.3 set, so that we can hear there is a problem
       // when playAtSpeedTB not found.
       double speed = 1.3;
-      const auto &settings = ProjectSettings::Get( *mProject );
-      speed = settings.GetPlaySpeed();
+      const auto &projectAudioIO = ProjectAudioIO::Get( *mProject );
+      speed = projectAudioIO.GetPlaySpeed();
       mOptions.minSpeed = speed -0.01;
       mOptions.maxSpeed = speed +0.01;
       mOptions.adjustStart = false;