Files
plezy/linux/runner/mpv/mpv_player.h
T

234 lines
7.5 KiB
C++

#ifndef MPV_PLAYER_H_
#define MPV_PLAYER_H_
#include <epoxy/egl.h>
#include <epoxy/gl.h>
#include <gtk/gtk.h>
#include <mpv/client.h>
#include <mpv/render.h>
#include <mpv/render_gl.h>
#include <atomic>
#include <condition_variable>
#include <functional>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
#include <tuple>
#include <vector>
#include "../../../shared/mpv/mpv_player_common.h"
// Forward declaration for Flutter types
struct _FlValue;
namespace mpv {
/// Callback function type for mpv events.
/// Note: FlValue* is passed from the global namespace, not mpv namespace.
using EventCallback = std::function<void(::_FlValue*)>;
/// Callback for requesting a redraw (called from mpv render update thread).
using RedrawCallback = std::function<void()>;
/// Wrapper for libmpv that handles initialization, OpenGL rendering,
/// commands, properties, and event dispatching.
class MpvPlayer {
public:
/// |audio_only| runs mpv as a music core with video disabled entirely:
/// no render context is ever created (InitRenderContext must not be
/// called) and no GL/EGL state is touched.
explicit MpvPlayer(bool audio_only = false);
~MpvPlayer();
/// Initializes the mpv instance and configures options.
/// Does NOT create the render context — call InitRenderContext() later
/// when an OpenGL context is available.
/// @return true if initialization succeeded.
bool Initialize();
/// Creates the mpv OpenGL render context.
/// Must be called with a valid GL context current (e.g., from FlTextureGL::populate).
/// Fails on audio-only players.
/// @return true if render context creation succeeded.
bool InitRenderContext();
/// Returns true if the render context has been created.
bool HasRenderContext() const { return mpv_gl_ != nullptr; }
/// Returns the isolated EGL display used for mpv rendering.
EGLDisplay GetEglDisplay() const { return egl_display_; }
/// Returns the isolated EGL context used for mpv rendering.
EGLContext GetEglContext() const { return egl_context_; }
/// Disposes mpv and releases resources.
void Dispose();
/// Returns true if mpv is initialized (has both mpv handle and render
/// context; audio-only players never have a render context).
bool IsInitialized() const { return mpv_ != nullptr && (audio_only_ || mpv_gl_ != nullptr); }
/// Returns true if this player has been disposed.
bool IsDisposed() const { return disposed_.load(); }
/// Returns true if mpv handle exists (even without render context).
bool HasMpvHandle() 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);
/// Sets an mpv property by name.
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 value 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);
/// Renders a frame to the specified FBO.
void Render(int width, int height, int fbo = 0);
/// Reports that the mouse has moved.
void ReportMouseMove(int x, int y);
/// Sets the event callback for property changes and events.
void SetEventCallback(EventCallback callback);
/// Sets the redraw callback (called when mpv has a new frame ready).
void SetRedrawCallback(RedrawCallback callback);
/// Returns true if a redraw is needed.
bool NeedsRedraw() const { return needs_redraw_.load(); }
/// Clears the redraw flag.
void ClearRedrawFlag() { needs_redraw_.store(false); }
/// Sets the MPV log message level (e.g., "warn", "v", "debug").
void SetLogLevel(const std::string& level);
private:
class CallbackContext {
public:
class Lease {
public:
Lease() = default;
Lease(const Lease&) = delete;
Lease& operator=(const Lease&) = delete;
Lease(Lease&& other) noexcept;
Lease& operator=(Lease&& other) noexcept;
~Lease();
explicit operator bool() const { return player_ != nullptr; }
MpvPlayer* player() const { return player_; }
private:
friend class CallbackContext;
Lease(CallbackContext* context, MpvPlayer* player);
void Release();
CallbackContext* context_ = nullptr;
MpvPlayer* player_ = nullptr;
};
explicit CallbackContext(MpvPlayer* player);
~CallbackContext();
Lease Acquire();
void DetachAndWait();
GMainContext* main_context() const { return main_context_; }
private:
void ReleaseLease();
std::mutex mutex_;
std::condition_variable quiescent_;
MpvPlayer* player_;
size_t in_flight_ = 0;
GMainContext* main_context_;
};
struct SourceCallbackData;
friend class MpvPlayerLifecycleTestPeer;
/// MPV event wakeup callback (called from mpv thread).
static void OnMpvWakeup(void* ctx);
/// MPV render update callback (called when frame is ready).
static void OnMpvRenderUpdate(void* ctx);
static gboolean DispatchWakeupSource(gpointer data);
static gboolean DispatchRedrawSource(gpointer data);
static gboolean DispatchRecoverySource(gpointer data);
static void DestroySourceCallbackData(gpointer data);
void ScheduleWakeupSource();
void ScheduleRedrawSource();
void ScheduleRecoverySource();
void RemoveTrackedSources();
/// Processes pending mpv events.
bool ProcessEvents();
/// Handles a single mpv event.
void HandleMpvEvent(mpv_event* event);
/// Sends a property change notification.
void SendPropertyChange(const char* name, mpv_node* data);
/// Sends an event notification.
void SendEvent(const std::string& name, ::_FlValue* data = nullptr);
void MaybeRunAudioRecovery();
void TryAudioReload(const char* reason, int attempt);
void EnsureAudioRecoveryTimer();
void LogRecovery(const std::string& text);
void SetHDREnabled(bool enabled, StatusCallback callback = nullptr);
/// Helper to convert mpv_node to FlValue.
::_FlValue* NodeToFlValue(mpv_node* node);
const bool audio_only_;
mpv_handle* mpv_ = nullptr;
mpv_render_context* mpv_gl_ = nullptr;
// Isolated EGL context for mpv rendering (not shared with Flutter)
EGLDisplay egl_display_ = EGL_NO_DISPLAY;
EGLContext egl_context_ = EGL_NO_CONTEXT;
std::atomic<bool> needs_redraw_{false};
std::atomic<bool> disposed_{false};
EventCallback event_callback_;
RedrawCallback redraw_callback_;
std::mutex callback_mutex_;
plezy::mpv_common::AudioRecoveryState audio_recovery_;
plezy::mpv_common::AsyncRequestRegistry pending_requests_;
plezy::mpv_common::PropertyObservationRegistry observed_properties_;
bool hdr_enabled_ = true;
// All player-carrying sources are attached to CallbackContext::main_context()
// and protected by source_mutex_.
std::shared_ptr<CallbackContext> callback_context_;
std::mutex source_mutex_;
guint wakeup_source_id_ = 0;
guint redraw_source_id_ = 0;
guint recovery_source_id_ = 0;
};
} // namespace mpv
#endif // MPV_PLAYER_H_