Skip to content

Commit

Permalink
fix(Windows): Send messages on main thread (workaround).
Browse files Browse the repository at this point in the history
fixes #379
  • Loading branch information
llfbandit committed Dec 29, 2024
1 parent 4c4ba96 commit 37548ed
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 33 deletions.
3 changes: 3 additions & 0 deletions record_windows/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## 1.0.5
* fix: Send messages on main thread (workaround).

## 1.0.4
* fix: Process path as wide string (UTF-16).

Expand Down
2 changes: 1 addition & 1 deletion record_windows/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: record_windows
description: Windows specific implementation for record package called by record_platform_interface.
version: 1.0.4
version: 1.0.5
homepage: https://github.com/llfbandit/record/tree/master/record_windows

environment:
Expand Down
30 changes: 4 additions & 26 deletions record_windows/windows/record.cpp
Original file line number Diff line number Diff line change
@@ -1,29 +1,5 @@
/*
*
* https://learn.microsoft.com/en-us/windows/uwp/audio-video-camera/codec-query
* https://learn.microsoft.com/en-us/uwp/api/windows.media.core.codecsubtypes?view=winrt-22621
*
* https://github.com/microsoft/Windows-universal-samples/blob/main/Samples/CameraStarterKit/cpp/MainPage.xaml.h
* https://github.com/microsoft/Windows-universal-samples/blob/main/Samples/CameraStarterKit/cpp/MainPage.xaml.cpp
*
* https://learn.microsoft.com/en-us/windows/win32/medfound/audio-video-capture
* WMF FLAC https://stackoverflow.com/questions/48930499/how-do-i-encode-raw-48khz-32bits-pcm-to-flac-using-microsoft-media-foundation
* WMF AAC https://learn.microsoft.com/en-us/windows/win32/medfound/aac-encoder
* WMF https://learn.microsoft.com/en-us/windows/win32/medfound/audio-video-capture-in-media-foundation
* https://learn.microsoft.com/en-us/windows/win32/medfound/tutorial--using-the-sink-writer-to-encode-video
* https://learn.microsoft.com/en-us/windows/win32/medfound/audio-subtype-guids
* https://github.com/microsoft/Windows-classic-samples/tree/main/Samples/Win7Samples/multimedia/mediafoundation/wavsink
* https://learn.microsoft.com/fr-fr/windows/win32/api/_mf/
* https://stackoverflow.com/questions/12917256/windows-media-foundation-recording-audio
* https://github.com/sipsorcery/mediafoundationsamples/blob/master/MFAudioCaptureToSAR/MFAudioCaptureToSAR.cpp
* https://chromium-review.googlesource.com/c/chromium/src/+/3293969
*
* https://learn.microsoft.com/en-us/windows/win32/medfound/uncompressed-audio-media-types
*
* https://learn.microsoft.com/en-us/windows/win32/medfound/tutorial--encoding-an-mp4-file-
*/

#include "record.h"
#include "record_windows_plugin.h"

namespace record_windows
{
Expand Down Expand Up @@ -308,7 +284,9 @@ namespace record_windows
m_recordState = state;

if (m_stateEventHandler) {
m_stateEventHandler->Success(std::make_unique<flutter::EncodableValue>(state));
RecordWindowsPlugin::RunOnMainThread([this, state]() -> void {
m_stateEventHandler->Success(std::make_unique<flutter::EncodableValue>(state));
});
}
}

Expand Down
6 changes: 5 additions & 1 deletion record_windows/windows/record_readercallback.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "record.h"
#include "record_windows_plugin.h"

namespace record_windows
{
Expand Down Expand Up @@ -68,7 +69,10 @@ namespace record_windows
// Send data to stream when there's no writer
if (m_recordEventHandler && !m_pWriter) {
std::vector<uint8_t> bytes(pChunk, pChunk + size);
m_recordEventHandler->Success(std::make_unique<flutter::EncodableValue>(bytes));

RecordWindowsPlugin::RunOnMainThread([this, bytes]() -> void {
m_recordEventHandler->Success(std::make_unique<flutter::EncodableValue>(bytes));
});
}

GetAmplitude(pChunk, size, 2);
Expand Down
63 changes: 59 additions & 4 deletions record_windows/windows/record_windows_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,29 @@ namespace record_windows {
return hr;
}

void ErrorFromHR(HRESULT hr, MethodResult<EncodableValue>& result)
static void ErrorFromHR(HRESULT hr, MethodResult<EncodableValue>& result)
{
_com_error err(hr);
std::string errorText = Utf8FromUtf16(err.ErrorMessage());

result.Error("Record", "", EncodableValue(errorText));
}

static HWND GetRootWindow(flutter::FlutterView* view) {
return ::GetAncestor(view->GetNativeWindow(), GA_ROOT);
}

// static, Register the plugin
void RecordWindowsPlugin::RegisterWithRegistrar(flutter::PluginRegistrarWindows* registrar) {
auto plugin = std::make_unique<RecordWindowsPlugin>();
auto plugin = std::make_unique<RecordWindowsPlugin>(
[registrar](auto delegate) {
return registrar->RegisterTopLevelWindowProcDelegate(delegate);
},
[registrar](auto proc_id) {
registrar->UnregisterTopLevelWindowProcDelegate(proc_id);
},
[registrar] { return GetRootWindow(registrar->GetView()); }
);

m_binaryMessenger = registrar->messenger();

Expand All @@ -48,14 +60,56 @@ namespace record_windows {
registrar->AddPlugin(std::move(plugin));
}

RecordWindowsPlugin::RecordWindowsPlugin() {
// static
std::queue<std::function<void()>> RecordWindowsPlugin::callbacks{};

// static
FlutterRootWindowProvider RecordWindowsPlugin::get_root_window{};

// static
void RecordWindowsPlugin::RunOnMainThread(std::function<void()> callback) {
callbacks.push(callback);
PostMessage(get_root_window(), WM_RUN_DELEGATE, 0, 0);
}

RecordWindowsPlugin::RecordWindowsPlugin(
WindowProcDelegateRegistrator registrator,
WindowProcDelegateUnregistrator unregistrator,
FlutterRootWindowProvider window_provider
): m_win_proc_delegate_registrator(registrator),
m_win_proc_delegate_unregistrator(unregistrator) {

get_root_window = std::move(window_provider);

m_window_proc_id = m_win_proc_delegate_registrator(
[this](HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) {
return HandleWindowProc(hwnd, message, wparam, lparam);
}
);
}

RecordWindowsPlugin::~RecordWindowsPlugin() {
for (const auto& [recorderId, recorder] : m_recorders)
{
recorder->Dispose();
}

m_win_proc_delegate_unregistrator(m_window_proc_id);
}

std::optional<LRESULT> RecordWindowsPlugin::HandleWindowProc(HWND hwnd,
UINT message,
WPARAM wparam,
LPARAM lparam) {
std::optional<LRESULT> result;
switch (message) {
case WM_RUN_DELEGATE:
callbacks.front()();
callbacks.pop();
result = 0;
break;
}
return result;
}

// Called when a method is called on this plugin's channel from Dart.
Expand Down Expand Up @@ -190,7 +244,8 @@ namespace record_windows {
EncodableMap({
{EncodableValue("current"), EncodableValue(amp["current"])},
{EncodableValue("max"), EncodableValue(amp["max"])}
}))
}
))
);
}
else if (method_call.method_name().compare("isEncoderSupported") == 0)
Expand Down
33 changes: 32 additions & 1 deletion record_windows/windows/record_windows_plugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,45 @@

#include "utils.h"
#include "record.h"
#include <queue>

using namespace flutter;

#define WM_RUN_DELEGATE (WM_USER + 101)

namespace record_windows {
typedef flutter::EventSink<flutter::EncodableValue> FlEventSink;
typedef flutter::StreamHandlerError<flutter::EncodableValue> FlStreamHandlerError;

using FlutterRootWindowProvider = std::function<HWND()>;
using WindowProcDelegate = std::function<std::optional<LRESULT>(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam)>;
using WindowProcDelegateRegistrator = std::function<int(WindowProcDelegate delegate)>;
using WindowProcDelegateUnregistrator = std::function<void(int proc_id)>;

class RecordWindowsPlugin : public flutter::Plugin {
public:
static void RegisterWithRegistrar(flutter::PluginRegistrarWindows* registrar);

RecordWindowsPlugin();
RecordWindowsPlugin(
WindowProcDelegateRegistrator registrator,
WindowProcDelegateUnregistrator unregistrator,
FlutterRootWindowProvider window_provider
);
virtual ~RecordWindowsPlugin();

// Disallow copy and assign.
RecordWindowsPlugin(const RecordWindowsPlugin&) = delete;
RecordWindowsPlugin& operator=(const RecordWindowsPlugin&) = delete;

// The function to call to get the root window.
static FlutterRootWindowProvider get_root_window;

// A queue of callbacks to run on the main thread.
static std::queue<std::function<void()>> callbacks;

// Runs the given callback on the main thread.
static void RunOnMainThread(std::function<void()> callback);

private:
static inline BinaryMessenger* m_binaryMessenger;

Expand All @@ -50,6 +71,16 @@ namespace record_windows {
std::unique_ptr<RecordConfig> InitRecordConfig(const EncodableMap* args);

std::map<std::string, std::unique_ptr<Recorder>> m_recorders{};

// Called for top-level WindowProc delegation.
std::optional<LRESULT> HandleWindowProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam);

// The registrar for this plugin, for registering top-level WindowProc delegates.
WindowProcDelegateRegistrator m_win_proc_delegate_registrator;
WindowProcDelegateUnregistrator m_win_proc_delegate_unregistrator;

// The ID of the WindowProc delegate registration.
int m_window_proc_id = -1;
};

} // namespace record_windows
Expand Down

0 comments on commit 37548ed

Please sign in to comment.