44#include " win/Hr.h"
55
66#include < QDateTime>
7+ #include < QDebug>
78#include < QFileInfo>
89#include < QHash>
910#include < QMutex>
1011
12+ #include < atomic>
1113#include < unordered_map>
1214
1315#include < windows.h>
@@ -182,8 +184,8 @@ struct AudioWorker::Impl
182184 private:
183185 void ping ()
184186 {
185- if (m_worker)
186- QMetaObject::invokeMethod (m_worker, [this ]() { m_worker->scheduleSnapshot (); }, Qt::QueuedConnection);
187+ if (m_worker && !m_worker-> m_destroying . load () )
188+ QMetaObject::invokeMethod (m_worker, [this ]() { if (m_worker && !m_worker-> m_destroying . load ()) m_worker->scheduleSnapshot (); }, Qt::QueuedConnection);
187189 }
188190
189191 std::atomic<ULONG> m_ref{1 };
@@ -221,8 +223,8 @@ struct AudioWorker::Impl
221223
222224 HRESULT STDMETHODCALLTYPE OnNotify (PAUDIO_VOLUME_NOTIFICATION_DATA) override
223225 {
224- if (m_worker)
225- QMetaObject::invokeMethod (m_worker, [this ]() { m_worker->scheduleSnapshot (); }, Qt::QueuedConnection);
226+ if (m_worker && !m_worker-> m_destroying . load () )
227+ QMetaObject::invokeMethod (m_worker, [this ]() { if (m_worker && !m_worker-> m_destroying . load ()) m_worker->scheduleSnapshot (); }, Qt::QueuedConnection);
226228 return S_OK;
227229 }
228230
@@ -272,8 +274,8 @@ struct AudioWorker::Impl
272274 private:
273275 void ping ()
274276 {
275- if (m_worker)
276- QMetaObject::invokeMethod (m_worker, [this ]() { m_worker->scheduleSnapshot (); }, Qt::QueuedConnection);
277+ if (m_worker && !m_worker-> m_destroying . load () )
278+ QMetaObject::invokeMethod (m_worker, [this ]() { if (m_worker && !m_worker-> m_destroying . load ()) m_worker->scheduleSnapshot (); }, Qt::QueuedConnection);
277279 }
278280
279281 std::atomic<ULONG> m_ref{1 };
@@ -312,8 +314,8 @@ struct AudioWorker::Impl
312314
313315 HRESULT STDMETHODCALLTYPE OnSessionCreated (IAudioSessionControl *) override
314316 {
315- if (m_worker)
316- QMetaObject::invokeMethod (m_worker, [this ]() { m_worker->scheduleSnapshot (); }, Qt::QueuedConnection);
317+ if (m_worker && !m_worker-> m_destroying . load () )
318+ QMetaObject::invokeMethod (m_worker, [this ]() { if (m_worker && !m_worker-> m_destroying . load ()) m_worker->scheduleSnapshot (); }, Qt::QueuedConnection);
317319 return S_OK;
318320 }
319321
@@ -413,6 +415,7 @@ AudioWorker::AudioWorker(QObject *parent)
413415
414416AudioWorker::~AudioWorker ()
415417{
418+ m_destroying.store (true );
416419 stop ();
417420 delete m;
418421 m = nullptr ;
@@ -446,13 +449,15 @@ void AudioWorker::stop()
446449
447450void AudioWorker::setShowSystemSessions (bool show)
448451{
452+ if (m_destroying.load ())
453+ return ;
449454 m_showSystemSessions = show;
450455 scheduleSnapshot ();
451456}
452457
453458void AudioWorker::setDeviceVolume (const QString &deviceId, double volume01)
454459{
455- if (!m)
460+ if (m_destroying. load () || !m)
456461 return ;
457462 auto it = m->devices .find (deviceId);
458463 if (it == m->devices .end () || !it->second .endpoint )
@@ -462,7 +467,7 @@ void AudioWorker::setDeviceVolume(const QString &deviceId, double volume01)
462467
463468void AudioWorker::setDeviceMuted (const QString &deviceId, bool muted)
464469{
465- if (!m)
470+ if (m_destroying. load () || !m)
466471 return ;
467472 auto it = m->devices .find (deviceId);
468473 if (it == m->devices .end () || !it->second .endpoint )
@@ -472,7 +477,7 @@ void AudioWorker::setDeviceMuted(const QString &deviceId, bool muted)
472477
473478void AudioWorker::setSessionVolume (const QString &deviceId, quint32 pid, const QString &exePath, double volume01)
474479{
475- if (!m)
480+ if (m_destroying. load () || !m)
476481 return ;
477482 Impl::SessionKey key{deviceId, pid, exePath};
478483 auto it = m->sessions .find (key);
@@ -483,7 +488,7 @@ void AudioWorker::setSessionVolume(const QString &deviceId, quint32 pid, const Q
483488
484489void AudioWorker::setSessionMuted (const QString &deviceId, quint32 pid, const QString &exePath, bool muted)
485490{
486- if (!m)
491+ if (m_destroying. load () || !m)
487492 return ;
488493 Impl::SessionKey key{deviceId, pid, exePath};
489494 auto it = m->sessions .find (key);
@@ -494,13 +499,20 @@ void AudioWorker::setSessionMuted(const QString &deviceId, quint32 pid, const QS
494499
495500void AudioWorker::scheduleSnapshot ()
496501{
497- if (!m_snapshotTimer.isActive ())
502+ // Check if object is being destroyed or already destroyed
503+ if (m_destroying.load () || !m) {
504+ qWarning (" AudioWorker::scheduleSnapshot() called after destruction - this indicates a race condition with COM callbacks" );
505+ return ;
506+ }
507+ // Additional safety: check if timer is still valid (parent object might be destroyed)
508+ if (!m_snapshotTimer.isActive () && m_snapshotTimer.parent () == this )
498509 m_snapshotTimer.start ();
499510}
500511
501512void AudioWorker::emitSnapshotNow ()
502513{
503- if (!m || !m->enumerator )
514+ // Check if object is being destroyed or already destroyed
515+ if (m_destroying.load () || !m || !m->enumerator )
504516 return ;
505517
506518 // Refresh device list.
@@ -529,6 +541,10 @@ void AudioWorker::emitSnapshotNow()
529541
530542 // Rebuild COM caches each snapshot, but unregister previous callbacks to avoid dangling notifications.
531543 {
544+ // Check again before unregistering - destruction might have started
545+ if (m_destroying.load () || !m)
546+ return ;
547+
532548 for (auto &kv : m->devices ) {
533549 auto &dc = kv.second ;
534550 if (dc.endpoint && m->endpointCb ) {
@@ -555,6 +571,10 @@ void AudioWorker::emitSnapshotNow()
555571 const qint64 nowMs = QDateTime::currentMSecsSinceEpoch ();
556572
557573 for (UINT i = 0 ; i < count; ++i) {
574+ // Check periodically during long-running operation
575+ if (m_destroying.load () || !m)
576+ return ;
577+
558578 ComPtr<IMMDevice> dev;
559579 if (FAILED (coll->Item (i, dev.put ())) || !dev)
560580 continue ;
@@ -582,7 +602,10 @@ void AudioWorker::emitSnapshotNow()
582602 ep->GetMute (&mute);
583603 ds.volume = vol;
584604 ds.muted = (mute == TRUE );
585- ep->RegisterControlChangeNotify (m->endpointCb .get ());
605+ // Only register callback if not destroying
606+ if (!m_destroying.load () && m && m->endpointCb ) {
607+ ep->RegisterControlChangeNotify (m->endpointCb .get ());
608+ }
586609 dc.endpoint .attach (ep.detach ());
587610 }
588611 }
@@ -592,7 +615,10 @@ void AudioWorker::emitSnapshotNow()
592615 ComPtr<IAudioSessionManager2> mgr;
593616 hr = dc.device ->Activate (__uuidof (IAudioSessionManager2), CLSCTX_ALL, nullptr , reinterpret_cast <void **>(mgr.put ()));
594617 if (SUCCEEDED (hr) && mgr) {
595- mgr->RegisterSessionNotification (m->sessionCb .get ());
618+ // Only register callback if not destroying
619+ if (!m_destroying.load () && m && m->sessionCb ) {
620+ mgr->RegisterSessionNotification (m->sessionCb .get ());
621+ }
596622 dc.sessionMgr .attach (mgr.detach ());
597623
598624 ComPtr<IAudioSessionEnumerator> se;
@@ -676,34 +702,45 @@ void AudioWorker::emitSnapshotNow()
676702
677703 sc.lastActiveMs = lastActive;
678704 sc.state = st;
679- auto *events = new Impl::SessionEvents (this , key);
680- if (sc.ctrl ) {
681- // RegisterAudioSessionNotification does NOT guarantee AddRef on events across all implementations,
682- // so we keep an explicit ref we own.
683- events->AddRef ();
684- sc.ctrl ->RegisterAudioSessionNotification (events);
685- sc.events = events;
705+ // Only register session events if not destroying
706+ if (!m_destroying.load () && m) {
707+ auto *events = new Impl::SessionEvents (this , key);
708+ if (sc.ctrl ) {
709+ // RegisterAudioSessionNotification does NOT guarantee AddRef on events across all implementations,
710+ // so we keep an explicit ref we own.
711+ events->AddRef ();
712+ sc.ctrl ->RegisterAudioSessionNotification (events);
713+ sc.events = events;
714+ }
715+ events->Release (); // balance initial ref
686716 }
687- events->Release (); // balance initial ref
688-
689- m->sessions .insert ({key, std::move (sc)});
690717
691- ds.sessions .push_back (ss);
718+ // Only store session if not destroying
719+ if (!m_destroying.load () && m) {
720+ m->sessions .insert ({key, std::move (sc)});
721+ ds.sessions .push_back (ss);
722+ }
692723 }
693724 }
694725 }
695726 }
696727
697- m->devices .emplace (id, std::move (dc));
698- devices.push_back (ds);
728+ // Final check before storing - don't add to cache if destroying
729+ if (!m_destroying.load () && m) {
730+ m->devices .emplace (id, std::move (dc));
731+ devices.push_back (ds);
732+ }
699733 }
700734
701- emit snapshotReady (devices);
735+ // Only emit if not destroying
736+ if (!m_destroying.load () && m)
737+ emit snapshotReady (devices);
702738}
703739
704740void AudioWorker::emitPeaksNow ()
705741{
706- if (!m)
742+ // Check if object is being destroyed or already destroyed
743+ if (m_destroying.load () || !m)
707744 return ;
708745
709746 QVector<SessionPeak> peaks;
0 commit comments