Files
Joel Winarske 73c9e1d72d audioplayers_linux: thread-safety refactor, lint pass
Audit pass against the plugin turned up three latent
lifetime/ownership bugs and a handful of data races. Rewriting
AudioPlayer to hold shared state by shared_ptr fixes them.

Lifetime / ownership
--------------------
Previously, SendEvent and OnError queued g_idle_add_full callbacks
that captured `this` by pointer. StartDurationDiscovery spawned a
detached thread writing to `&discovered_duration_ms_`. All three
would dereference freed memory if the AudioPlayer was destroyed
before the callback/thread fired.

Fix: introduce a SharedState struct (event sink + mutex + cancelled
flag + channel name + discovered_duration_ms atomic) owned by a
shared_ptr on the AudioPlayer. Idle callbacks and the discovery
thread capture the shared_ptr by value, extending the state's
lifetime past AudioPlayer destruction. The destructor sets
cancelled=true and clears the sink under the mutex; late-firing
callbacks observe cancelled and become no-ops.

Also introduces a small PostToMainLoop helper that wraps
g_idle_add_full over a heap-allocated std::function, removing the
ad-hoc new-Ctx / delete-Ctx pattern in three places.

Data races
----------
isPlaying_, isInitialized_, isLooping_, isSeekCompleted_ were plain
bools accessed from the Flutter platform thread, the GStreamer bus
thread, and the streaming thread (AboutToFinish). Now std::atomic<bool>
with relaxed ordering (no synchronization semantics needed — they
only guard branches on other code paths that already have their own
synchronization). The isSeekCompleted→true transition in OnBusMessage
uses compare_exchange_strong so the OnSeekCompleted callback fires at
most once.

url_ (std::string) was written by SetSourceUrl on the platform thread
and read by AboutToFinish on the streaming thread. Now guarded by
url_mu_; AboutToFinish snapshots under the lock before setting
playbin's uri property.

Dead code
---------
Removed:
- GMainContext* context_ (captured in ctor, never read)
- GstElement* source_ and its Dispose-time unref (only ever assigned
  by a signal handler that has been empty since the Phase-2 SSL fix,
  so source_ was always null)
- g_signal_connect for "source-setup" and the empty SourceSetup
  callback itself
- #include <fstream>

Tooling
-------
- clang-tidy is clean (was: modernize-pass-by-value on the ctor,
  readability-use-anyofallof on the scheme whitelist loop — both
  fixed)
- clang-format applied across all plugin files

No functional change intended beyond the lifetime/race fixes.
Runtime-tested against the upstream Bluefire example: replay, gapless
loop, VBR MP3 duration, invalid-source error, multi-source swap all
still work.

Signed-off-by: Joel Winarske <joel.winarske@gmail.com>
2026-04-14 15:42:01 -07:00
..
2024-08-28 13:57:29 -07:00