Skip to content

Commit

Permalink
WIP Send player messages from the UI thread
Browse files Browse the repository at this point in the history
Fix for the following warning when AudioPlayer functions like
setAudioSource are called:

[ERROR:flutter/shell/common/shell.cc(1015)]
The 'com.ryanheise.just_audio.events....' channel sent a message from
native to Flutter on a non-platform thread. Platform channel messages
must be sent on the platform thread. Failure to do so may result in
data loss or crashes, and must be fixed in the plugin or application
code creating that channel...

WIP:

- At some point Flutter will support sending messages from any thread.
  Tracking issue: flutter/flutter#93945
  So this is mostly a stopgap solution.

- Alternatively to the enable_shared_from_this pattern, posted
  messages could capture weak pointers to the event sink. This would
  behave subtly differently when the sink pointer changes between
  posting the message and executing the handler (i.e., upon any
  change, the handler would see an empty pointer. This is possibly
  more correct).
  • Loading branch information
andreaszapf committed Dec 29, 2023
1 parent 1ffc510 commit a479139
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 24 deletions.
1 change: 1 addition & 0 deletions just_audio_windows/windows/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ set(PLUGIN_NAME "just_audio_windows_plugin")
add_library(${PLUGIN_NAME} SHARED
"just_audio_windows_plugin.cpp"
"player.hpp"
"ui_thread_handler.hpp"
)
apply_standard_settings(${PLUGIN_NAME})
set_target_properties(${PLUGIN_NAME} PROPERTIES
Expand Down
12 changes: 8 additions & 4 deletions just_audio_windows/windows/just_audio_windows_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include <sstream>

#include "player.hpp"
#include "ui_thread_handler.hpp"

using flutter::EncodableMap;
using flutter::EncodableValue;
Expand All @@ -24,7 +25,7 @@ class JustAudioWindowsPlugin : public flutter::Plugin {
public:
static void RegisterWithRegistrar(flutter::PluginRegistrarWindows *registrar);

JustAudioWindowsPlugin();
explicit JustAudioWindowsPlugin(flutter::PluginRegistrarWindows* registrar);

virtual ~JustAudioWindowsPlugin();

Expand All @@ -42,6 +43,7 @@ class JustAudioWindowsPlugin : public flutter::Plugin {
void DisposePlayerByPlayerId(std::string id);

std::vector<std::shared_ptr<AudioPlayer>> players_;
JustAudioUiThreadHandler uiThreadHandler_;
};

// static
Expand All @@ -52,7 +54,7 @@ void JustAudioWindowsPlugin::RegisterWithRegistrar(
registrar->messenger(), "com.ryanheise.just_audio.methods",
&flutter::StandardMethodCodec::GetInstance());

auto plugin = std::make_unique<JustAudioWindowsPlugin>();
auto plugin = std::make_unique<JustAudioWindowsPlugin>(registrar);

channel->SetMethodCallHandler(
[plugin_pointer = plugin.get(), messenger_pointer = registrar->messenger()](const auto &call, auto result) {
Expand All @@ -62,7 +64,9 @@ void JustAudioWindowsPlugin::RegisterWithRegistrar(
registrar->AddPlugin(std::move(plugin));
}

JustAudioWindowsPlugin::JustAudioWindowsPlugin() {}
JustAudioWindowsPlugin::JustAudioWindowsPlugin(flutter::PluginRegistrarWindows* registrar)
: uiThreadHandler_(registrar) {
}

JustAudioWindowsPlugin::~JustAudioWindowsPlugin() {}

Expand All @@ -77,7 +81,7 @@ void JustAudioWindowsPlugin::HandleMethodCall(
if (!id) {
return result->Error("argument_error", "id argument missing");
}
auto player = AudioPlayer::Create(*id, messenger);
auto player = AudioPlayer::Create(*id, messenger, &uiThreadHandler_);
players_.push_back(std::move(player));
result->Success();
} else if (method_call.method_name().compare("disposePlayer") == 0) {
Expand Down
68 changes: 48 additions & 20 deletions just_audio_windows/windows/player.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
#include <winrt/Windows.Media.Playback.h>
#include <winrt/Windows.System.h>

#include "ui_thread_handler.hpp"

#define TO_MILLISECONDS(timespan) timespan.count() / 10000
#define TO_MICROSECONDS(timespan) TO_MILLISECONDS(timespan) * 1000

Expand Down Expand Up @@ -89,13 +91,28 @@ std::string unescapeUri(std::string uri) {
}


class JustAudioEventSink {
class JustAudioEventSink : public std::enable_shared_from_this<JustAudioEventSink> {
private:
struct PrivateConstructionTag {};

public:
// Prevent copying.
JustAudioEventSink(JustAudioEventSink const&) = delete;
JustAudioEventSink& operator=(JustAudioEventSink const&) = delete;

JustAudioEventSink::JustAudioEventSink(flutter::BinaryMessenger* messenger, const std::string& id) {
static std::shared_ptr<JustAudioEventSink> Create(
flutter::BinaryMessenger* messenger, const std::string& id, JustAudioUiThreadHandler* uiThreadHandler) {
auto eventSink = std::make_shared<JustAudioEventSink>(PrivateConstructionTag{});
eventSink->Initialize(messenger, id, uiThreadHandler, PrivateConstructionTag{});
return eventSink;
}

JustAudioEventSink(PrivateConstructionTag) {}

void Initialize(
flutter::BinaryMessenger* messenger, const std::string& id, JustAudioUiThreadHandler* uiHandler, PrivateConstructionTag) {
uiThreadHandler = uiHandler;

auto event_channel =
std::make_unique<flutter::EventChannel<flutter::EncodableValue>>(messenger, id, &flutter::StandardMethodCodec::GetInstance());

Expand All @@ -111,20 +128,29 @@ class JustAudioEventSink {
event_channel->SetStreamHandler(std::move(event_handler));
}

void Success(const EncodableValue& event) {
if (sink) {
sink->Success(event);
}
void PostSuccess(const EncodableValue& event) {
uiThreadHandler->Post([event, weakSelf = weak_from_this()] {
if (auto self = weakSelf.lock()) {
if (self->sink) {
self->sink->Success(event);
}
}
});
}

void Error(const std::string& error_code,
const std::string& error_message) {
if (sink) {
sink->Error(error_code, error_message);
}
void PostError(const std::string& error_code, const std::string& error_message) {
uiThreadHandler->Post([error_code, error_message, weakSelf = weak_from_this()] {
if (auto self = weakSelf.lock()) {
if (self->sink) {
self->sink->Error(error_code, error_message);
}
}
});
}

private:
std::unique_ptr<flutter::EventSink<>> sink = nullptr;
JustAudioUiThreadHandler* uiThreadHandler = nullptr;
};

class AudioPlayer : public std::enable_shared_from_this<AudioPlayer> {
Expand All @@ -137,8 +163,8 @@ class AudioPlayer : public std::enable_shared_from_this<AudioPlayer> {
Playback::MediaPlaybackList mediaPlaybackList{};

std::unique_ptr<flutter::MethodChannel<flutter::EncodableValue>> player_channel_;
std::unique_ptr<JustAudioEventSink> event_sink_ = nullptr;
std::unique_ptr<JustAudioEventSink> data_sink_ = nullptr;
std::shared_ptr<JustAudioEventSink> event_sink_;
std::shared_ptr<JustAudioEventSink> data_sink_;

static std::shared_ptr<AudioPlayer> Create(
std::string idx, flutter::BinaryMessenger* messenger) {
Expand All @@ -150,7 +176,7 @@ class AudioPlayer : public std::enable_shared_from_this<AudioPlayer> {
AudioPlayer::AudioPlayer(PrivateConstructionTag) {}

void AudioPlayer::Initialize(
std::string idx, flutter::BinaryMessenger* messenger, PrivateConstructionTag) {
std::string idx, flutter::BinaryMessenger* messenger, JustAudioUiThreadHandler* uiThreadHandler, PrivateConstructionTag) {
id = idx;

// Set up channels
Expand All @@ -165,8 +191,10 @@ class AudioPlayer : public std::enable_shared_from_this<AudioPlayer> {
player->HandleMethodCall(call, std::move(result));
});

event_sink_ = std::make_unique<JustAudioEventSink>(messenger, "com.ryanheise.just_audio.events." + idx);
data_sink_ = std::make_unique<JustAudioEventSink>(messenger, "com.ryanheise.just_audio.data." + idx);
event_sink_ = JustAudioEventSink::Create(
messenger, "com.ryanheise.just_audio.events." + idx, uiThreadHandler);
data_sink_ = JustAudioEventSink::Create(
messenger, "com.ryanheise.just_audio.data." + idx, uiThreadHandler);

/// Set up event callbacks
// Playback event
Expand Down Expand Up @@ -205,7 +233,7 @@ class AudioPlayer : public std::enable_shared_from_this<AudioPlayer> {
}

if (auto self = weakSelf.lock()) {
self->event_sink_->Error(code, errorMessage);
self->event_sink_->PostError(code, errorMessage);
}
});

Expand Down Expand Up @@ -246,7 +274,7 @@ class AudioPlayer : public std::enable_shared_from_this<AudioPlayer> {
}

if (auto self = weakSelf.lock()) {
self->event_sink_->Error(code, message);
self->event_sink_->PostError(code, message);
}
});
}
Expand Down Expand Up @@ -569,7 +597,7 @@ class AudioPlayer : public std::enable_shared_from_this<AudioPlayer> {
eventData[flutter::EncodableValue("currentIndex")] = flutter::EncodableValue(0); //int
}

event_sink_->Success(eventData);
event_sink_->PostSuccess(eventData);
}

int AudioPlayer::processingState(Playback::MediaPlaybackState state) {
Expand Down Expand Up @@ -599,7 +627,7 @@ class AudioPlayer : public std::enable_shared_from_this<AudioPlayer> {
eventData[flutter::EncodableValue("loopMode")] = flutter::EncodableValue(getLoopMode());
eventData[flutter::EncodableValue("shuffleMode")] = flutter::EncodableValue(getShuffleMode());

data_sink_->Success(eventData);
data_sink_->PostSuccess(eventData);
}

int AudioPlayer::getLoopMode() {
Expand Down
70 changes: 70 additions & 0 deletions just_audio_windows/windows/ui_thread_handler.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#pragma once

#include <windows.h>

#include <flutter/plugin_registrar_windows.h>

#include <algorithm>
#include <functional>
#include <optional>
#include <mutex>

class JustAudioUiThreadHandler
{
public:
explicit JustAudioUiThreadHandler(flutter::PluginRegistrarWindows* registrar)
: registrar_(registrar) {
windowProcId_ = registrar_->RegisterTopLevelWindowProcDelegate(
[this](HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) {
return HandleWindowMessage(hwnd, message, wparam, lparam);
});
}

~JustAudioUiThreadHandler() {
registrar_->UnregisterTopLevelWindowProcDelegate(windowProcId_);
}

JustAudioUiThreadHandler(const JustAudioUiThreadHandler&) = delete;
JustAudioUiThreadHandler& operator=(const JustAudioUiThreadHandler&) = delete;

void Post(std::function<void()>&& func)
{
std::lock_guard<std::mutex> lock(mutex_);
queuedFuncs_.emplace_back(std::move(func));
Notify();
}

private:
static const UINT kWmCallQueuedFunctions = WM_APP + 0x1d7;

void Notify() {
if (hwnd_ != 0) {
PostMessage(hwnd_, kWmCallQueuedFunctions, 0, reinterpret_cast<LPARAM>(this));
}
}

std::optional<LRESULT>HandleWindowMessage(
HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) {
if (hwnd_ == 0) {
hwnd_ = hwnd;
Notify(); // Make sure queued functions are processed
}
if (message == kWmCallQueuedFunctions && lparam == reinterpret_cast<LPARAM>(this)) {
std::list<std::function<void()>> queuedFuncs;
{
std::lock_guard<std::mutex> lock(mutex_);
std::swap(queuedFuncs_, queuedFuncs);
}
for (auto& func : queuedFuncs) {
func();
}
}
return std::nullopt;
}

flutter::PluginRegistrarWindows* registrar_;
int windowProcId_ = 0;
HWND hwnd_ = 0;
std::list<std::function<void()>> queuedFuncs_;
std::mutex mutex_;
};

0 comments on commit a479139

Please sign in to comment.