fix: recover audio after Windows sleep via ao-null-reload

This commit is contained in:
edde746
2026-04-03 12:19:36 +02:00
parent 122cff359d
commit 062fd82de1
4 changed files with 17 additions and 46 deletions
-17
View File
@@ -124,11 +124,6 @@ std::optional<HRESULT> MpvCore::WindowProc(HWND hwnd, UINT message,
::RedrawWindow(flutter_window_, nullptr, nullptr, ::RedrawWindow(flutter_window_, nullptr, nullptr,
RDW_INVALIDATE | RDW_UPDATENOW | RDW_ALLCHILDREN); RDW_INVALIDATE | RDW_UPDATENOW | RDW_ALLCHILDREN);
} }
} else if (wparam == kPowerResumeTimerId) {
::KillTimer(flutter_window_, kPowerResumeTimerId);
if (power_resume_cb_) {
power_resume_cb_();
}
} }
break; break;
} }
@@ -149,14 +144,6 @@ std::optional<HRESULT> MpvCore::WindowProc(HWND hwnd, UINT message,
} }
break; break;
} }
case WM_POWERBROADCAST: {
if (wparam == PBT_APMRESUMEAUTOMATIC && power_resume_cb_) {
// Delay audio reload to let WASAPI fully reinitialize after wake.
::KillTimer(flutter_window_, kPowerResumeTimerId);
::SetTimer(flutter_window_, kPowerResumeTimerId, kPowerResumeAudioDelay, nullptr);
}
break;
}
case WM_CLOSE: { case WM_CLOSE: {
::SendMessage(container_, WM_CLOSE, 0, 0); ::SendMessage(container_, WM_CLOSE, 0, 0);
for (const auto& [mpv_view, rect] : mpv_views_) { for (const auto& [mpv_view, rect] : mpv_views_) {
@@ -207,10 +194,6 @@ void MpvCore::DisableComposition() {
::DwmFlush(); ::DwmFlush();
} }
void MpvCore::SetPowerResumeCallback(PowerResumeCallback callback) {
power_resume_cb_ = std::move(callback);
}
std::unique_ptr<MpvCore> MpvCore::instance_ = nullptr; std::unique_ptr<MpvCore> MpvCore::instance_ = nullptr;
} // namespace mpv } // namespace mpv
-8
View File
@@ -4,7 +4,6 @@
#include <Windows.h> #include <Windows.h>
#include <cmath> #include <cmath>
#include <functional>
#include <map> #include <map>
#include <memory> #include <memory>
#include <optional> #include <optional>
@@ -16,9 +15,7 @@ namespace mpv {
class MpvCore { class MpvCore {
public: public:
static constexpr auto kPositionAndShowDelay = 300; static constexpr auto kPositionAndShowDelay = 300;
static constexpr auto kPowerResumeAudioDelay = 500;
static constexpr UINT_PTR kCompositionRestoreTimerId = 1001; static constexpr UINT_PTR kCompositionRestoreTimerId = 1001;
static constexpr UINT_PTR kPowerResumeTimerId = 1002;
static MpvCore* GetInstance(); static MpvCore* GetInstance();
static void SetInstance(std::unique_ptr<MpvCore> instance); static void SetInstance(std::unique_ptr<MpvCore> instance);
@@ -45,10 +42,6 @@ class MpvCore {
std::optional<HRESULT> WindowProc(HWND hwnd, UINT message, WPARAM wparam, std::optional<HRESULT> WindowProc(HWND hwnd, UINT message, WPARAM wparam,
LPARAM lparam); LPARAM lparam);
// Callback invoked when Windows resumes from sleep/hibernate.
using PowerResumeCallback = std::function<void()>;
void SetPowerResumeCallback(PowerResumeCallback callback);
private: private:
RECT GetGlobalRect(int32_t left, int32_t top, int32_t right, int32_t bottom); RECT GetGlobalRect(int32_t left, int32_t top, int32_t right, int32_t bottom);
void EnableComposition(); void EnableComposition();
@@ -62,7 +55,6 @@ class MpvCore {
bool was_window_hidden_due_to_minimize_ = false; bool was_window_hidden_due_to_minimize_ = false;
bool visible_ = true; bool visible_ = true;
bool composition_enabled_ = false; bool composition_enabled_ = false;
PowerResumeCallback power_resume_cb_;
static std::unique_ptr<MpvCore> instance_; static std::unique_ptr<MpvCore> instance_;
}; };
+17
View File
@@ -53,6 +53,11 @@ bool MpvPlayer::Initialize(HWND container, HWND flutter_window) {
mpv_set_option_string(mpv_, "tone-mapping", "auto"); mpv_set_option_string(mpv_, "tone-mapping", "auto");
mpv_set_option_string(mpv_, "hdr-compute-peak", "auto"); mpv_set_option_string(mpv_, "hdr-compute-peak", "auto");
// When WASAPI becomes unavailable (sleep, device unplug), fall back to null
// audio output instead of permanently dropping the audio track. Recovery is
// handled in the event loop when audio-device-list changes.
mpv_set_option_string(mpv_, "audio-fallback-to-null", "yes");
// Default to warn-level logging; Dart side can raise to "v" if debug logging is enabled. // Default to warn-level logging; Dart side can raise to "v" if debug logging is enabled.
mpv_request_log_messages(mpv_, "warn"); mpv_request_log_messages(mpv_, "warn");
@@ -355,6 +360,18 @@ void MpvPlayer::HandleMpvEvent(mpv_event* event) {
UpdateHDRMode(sigPeak); UpdateHDRMode(sigPeak);
} }
// Audio recovery: when the device list changes and audio has fallen back
// to null output (e.g. after sleep/wake or device unplug), re-set
// audio-device to switch back to the real output.
// Mirrors mpv's TOOLS/lua/ao-null-reload.lua for embedded libmpv.
if (strcmp(prop->name, "audio-device-list") == 0 &&
GetProperty("current-ao") == "null") {
auto device = GetProperty("audio-device");
if (!device.empty()) {
mpv_set_property_string(mpv_, "audio-device", device.c_str());
}
}
SendPropertyChange(prop->name, &node); SendPropertyChange(prop->name, &node);
break; break;
} }
-21
View File
@@ -65,12 +65,6 @@ MpvPlayerPlugin::MpvPlayerPlugin(flutter::PluginRegistrarWindows* registrar)
} }
MpvPlayerPlugin::~MpvPlayerPlugin() { MpvPlayerPlugin::~MpvPlayerPlugin() {
// Clear power-resume callback and kill pending timer so they cannot
// invoke a dangling `this`.
if (MpvCore::GetInstance()) {
MpvCore::GetInstance()->SetPowerResumeCallback(nullptr);
::KillTimer(GetWindow(), MpvCore::kPowerResumeTimerId);
}
// Unregister window proc delegate. // Unregister window proc delegate.
if (proc_id_) { if (proc_id_) {
registrar_->UnregisterTopLevelWindowProcDelegate(proc_id_.value()); registrar_->UnregisterTopLevelWindowProcDelegate(proc_id_.value());
@@ -135,16 +129,6 @@ void MpvPlayerPlugin::HandleMethodCall(
RECT rect = {0, 0, 100, 100}; RECT rect = {0, 0, 100, 100};
MpvCore::GetInstance()->CreateMpvView(player_->GetHwnd(), rect, 1.0); MpvCore::GetInstance()->CreateMpvView(player_->GetHwnd(), rect, 1.0);
// Reload audio output on wake from sleep to recover stale WASAPI sessions.
MpvCore::GetInstance()->SetPowerResumeCallback([this]() {
if (player_ && player_->IsInitialized()) {
auto device = player_->GetProperty("audio-device");
if (!device.empty()) {
player_->SetProperty("audio-device", device);
}
}
});
// Start hidden. // Start hidden.
MpvCore::GetInstance()->SetVisible(false); MpvCore::GetInstance()->SetVisible(false);
result->Success(flutter::EncodableValue(true)); result->Success(flutter::EncodableValue(true));
@@ -153,11 +137,6 @@ void MpvPlayerPlugin::HandleMethodCall(
result->Error("INIT_FAILED", "Failed to initialize MPV player"); result->Error("INIT_FAILED", "Failed to initialize MPV player");
} }
} else if (method == "dispose") { } else if (method == "dispose") {
// Clear power-resume callback and kill pending timer before disposing.
if (MpvCore::GetInstance()) {
MpvCore::GetInstance()->SetPowerResumeCallback(nullptr);
::KillTimer(GetWindow(), MpvCore::kPowerResumeTimerId);
}
if (player_) { if (player_) {
auto hwnd = player_->GetHwnd(); auto hwnd = player_->GetHwnd();
player_->Dispose(); player_->Dispose();