Files
edde746 9e8cfbc7dd fix(windows): keep video-child input on the Flutter view
Create the mpv host window with WS_DISABLED so Windows skips the video subtree
when it picks the window that owns a contact and hands the input to the parent
Flutter view instead. Touch over the video never reached Flutter before: mpv's
inner window owns the contact from its own thread, and neither relay worked
from there - Flutter resolves WM_POINTER with GetPointerInfo, which only
answers for a message the calling thread retrieved, and the system discards a
cross-thread pointer send outright. WS_EX_TRANSPARENT and an HTTRANSPARENT
WM_NCHITTEST reply are both same-thread-only, so disabling the subtree is the
one hit-test opt-out that applies across threads. The mouse relay stays for
input that still reaches mpv's window.

Repair the contract test that covers this. It drove its pointer assertions with
cross-thread sends that Windows drops, so every touch assertion had been dead
since it was added and the suite fails "primary touch must press once" on main.
Relaying those sends through the window's own thread runs all eight tests, and
injected mouse and touch presses over the disabled host now assert delivery to
the parent view; removing WS_DISABLED fails the suite.

close #1556
2026-07-26 19:04:36 +02:00

135 lines
4.5 KiB
C++

#ifndef MPV_PLAYER_H_
#define MPV_PLAYER_H_
#include <Windows.h>
#include <flutter/encodable_value.h>
#include <mpv/client.h>
#include <atomic>
#include <chrono>
#include <functional>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
#include <vector>
#include "../../../shared/mpv/mpv_player_common.h"
namespace mpv {
struct InnerWindowSubclassState;
// Style of the window mpv renders into. WS_DISABLED takes the host, and every
// window mpv creates inside it, out of input targeting so mouse, touch, and pen
// over the video reach the parent Flutter view instead of mpv's thread.
inline constexpr DWORD kVideoHostWindowStyle = WS_CHILD | WS_CLIPSIBLINGS | WS_DISABLED;
// Wrapper for libmpv that handles initialization, commands, properties,
// and event dispatching.
class MpvPlayer {
public:
using EventCallback = std::function<void(const flutter::EncodableValue&)>;
// |audio_only| runs mpv as a windowless music core: no child HWND, no VO,
// video decode disabled entirely (vid=no).
explicit MpvPlayer(bool audio_only = false);
~MpvPlayer();
// Initializes mpv and creates the video window as a child of the Flutter
// |view| window. The flutter-plezy engine presents the UI on a topmost
// DirectComposition visual, so the video child composites beneath it in the
// same HWND. In audio-only mode |view| is ignored (pass nullptr) and no
// window is created.
bool Initialize(HWND view);
// Disposes mpv and the video window.
void Dispose();
// Returns true if mpv is initialized.
bool IsInitialized() const { return mpv_ != nullptr; }
// Queues an mpv command without waiting for completion.
void Command(const std::vector<std::string>& args);
// Callback types for async mpv requests.
using StatusCallback = plezy::mpv_common::StatusCallback;
using CommandCallback = StatusCallback;
using GetPropertyCallback = plezy::mpv_common::GetPropertyCallback;
// Executes an mpv command asynchronously to prevent UI blocking.
void CommandAsync(const std::vector<std::string>& args, CommandCallback callback);
// Queues an mpv property update without waiting for completion.
void SetProperty(const std::string& name, const std::string& value);
// Sets an mpv property asynchronously.
void SetPropertyAsync(const std::string& name, const std::string& value, StatusCallback callback);
// Gets an mpv property asynchronously.
void GetPropertyAsync(const std::string& name, GetPropertyCallback callback);
// Observes an mpv property for changes.
void ObserveProperty(const std::string& name, const std::string& format, int id);
// Returns the mpv video window handle.
HWND GetHwnd() const { return hwnd_; }
// Updates the video window position.
void SetRect(RECT rect, double device_pixel_ratio);
// Shows or hides the video window.
void SetVisible(bool visible);
// Sets the MPV log message level (e.g., "warn", "v", "debug").
void SetLogLevel(const std::string& level);
// Sets the event callback for property changes and events.
void SetEventCallback(EventCallback callback);
// Power notifications, called from the platform thread (window proc).
// NotifyPowerResume only sets an atomic flag consumed by the event thread —
// no mpv calls, no timers — so it cannot race Dispose and needs no cleanup.
void NotifyPowerSuspend();
void NotifyPowerResume();
private:
friend class MpvPlayerPropertyContractTestPeer;
void StartEventLoop();
void StopEventLoop();
void EventLoop();
void HandleMpvEvent(mpv_event* event);
void SendPropertyChange(const char* name, mpv_node* data);
void SendEvent(const std::string& name, const flutter::EncodableMap& data = {});
void MaybeRunAudioRecovery();
void TryAudioReload(const char* reason, int attempt, uint64_t request_generation);
void LogRecovery(const std::string& text);
void EnsureMpvInnerSubclassed();
void DetachMpvInnerSubclass();
const bool audio_only_;
mpv_handle* mpv_ = nullptr;
HWND hwnd_ = nullptr;
HWND forward_target_view_ = nullptr;
std::mutex inner_subclass_mutex_;
std::shared_ptr<InnerWindowSubclassState> inner_subclass_;
std::thread event_thread_;
std::atomic<bool> running_{false};
EventCallback event_callback_;
std::mutex callback_mutex_;
plezy::mpv_common::AudioRecoveryState audio_recovery_;
plezy::mpv_common::AsyncRequestRegistry pending_requests_;
plezy::mpv_common::PropertyObservationRegistry observed_properties_;
// HDR state
bool hdr_enabled_ = true;
void SetHDREnabled(bool enabled, StatusCallback callback = nullptr);
};
} // namespace mpv
#endif // MPV_PLAYER_H_