perf: compact [propId, value] wire format for property events
This commit is contained in:
@@ -29,6 +29,7 @@ class ExoPlayerPlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
|
||||
private var usingMpvFallback: Boolean = false
|
||||
private var activity: Activity? = null
|
||||
private var activityBinding: ActivityPluginBinding? = null
|
||||
private val nameToId = mutableMapOf<String, Int>()
|
||||
|
||||
// FlutterPlugin
|
||||
|
||||
@@ -119,6 +120,7 @@ class ExoPlayerPlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
|
||||
"getStats" -> handleGetStats(result)
|
||||
"getPlayerType" -> result.success(if (usingMpvFallback) "mpv" else "exoplayer")
|
||||
"setSubtitleStyle" -> handleSetSubtitleStyle(call, result)
|
||||
"observeProperty" -> handleObserveProperty(call, result)
|
||||
else -> result.notImplemented()
|
||||
}
|
||||
}
|
||||
@@ -413,6 +415,19 @@ class ExoPlayerPlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
|
||||
result.success(null)
|
||||
}
|
||||
|
||||
private fun handleObserveProperty(call: MethodCall, result: MethodChannel.Result) {
|
||||
val name = call.argument<String>("name")
|
||||
val id = call.argument<Int>("id")
|
||||
|
||||
if (name == null || id == null) {
|
||||
result.error("INVALID_ARGS", "Missing 'name' or 'id'", null)
|
||||
return
|
||||
}
|
||||
|
||||
nameToId[name] = id
|
||||
result.success(null)
|
||||
}
|
||||
|
||||
private fun handleSetSubtitleStyle(call: MethodCall, result: MethodChannel.Result) {
|
||||
val fontSize = call.argument<Number>("fontSize")?.toFloat() ?: 55f
|
||||
val textColor = call.argument<String>("textColor") ?: "#FFFFFF"
|
||||
@@ -508,13 +523,8 @@ class ExoPlayerPlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
|
||||
// ExoPlayerDelegate
|
||||
|
||||
override fun onPropertyChange(name: String, value: Any?) {
|
||||
eventSink?.success(
|
||||
mapOf(
|
||||
"type" to "property",
|
||||
"name" to name,
|
||||
"value" to value
|
||||
)
|
||||
)
|
||||
val propId = nameToId[name] ?: return
|
||||
eventSink?.success(listOf(propId, value))
|
||||
}
|
||||
|
||||
override fun onEvent(name: String, data: Map<String, Any>?) {
|
||||
|
||||
@@ -25,6 +25,7 @@ class MpvPlayerPlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
|
||||
private var playerCore: MpvPlayerCore? = null
|
||||
private var activity: Activity? = null
|
||||
private var activityBinding: ActivityPluginBinding? = null
|
||||
private val nameToId = mutableMapOf<String, Int>()
|
||||
|
||||
// FlutterPlugin
|
||||
|
||||
@@ -175,12 +176,14 @@ class MpvPlayerPlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
|
||||
private fun handleObserveProperty(call: MethodCall, result: MethodChannel.Result) {
|
||||
val name = call.argument<String>("name")
|
||||
val format = call.argument<String>("format")
|
||||
val id = call.argument<Int>("id")
|
||||
|
||||
if (name == null || format == null) {
|
||||
result.error("INVALID_ARGS", "Missing 'name' or 'format'", null)
|
||||
if (name == null || format == null || id == null) {
|
||||
result.error("INVALID_ARGS", "Missing 'name', 'format', or 'id'", null)
|
||||
return
|
||||
}
|
||||
|
||||
nameToId[name] = id
|
||||
playerCore?.observeProperty(name, format)
|
||||
result.success(null)
|
||||
}
|
||||
@@ -272,13 +275,8 @@ class MpvPlayerPlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
|
||||
// MpvPlayerDelegate
|
||||
|
||||
override fun onPropertyChange(name: String, value: Any?) {
|
||||
eventSink?.success(
|
||||
mapOf(
|
||||
"type" to "property",
|
||||
"name" to name,
|
||||
"value" to value
|
||||
)
|
||||
)
|
||||
val propId = nameToId[name] ?: return
|
||||
eventSink?.success(listOf(propId, value))
|
||||
}
|
||||
|
||||
override fun onEvent(name: String, data: Map<String, Any>?) {
|
||||
|
||||
@@ -9,6 +9,7 @@ class MpvPlayerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, MpvPlayerD
|
||||
private var playerCore: MpvPlayerCore?
|
||||
private var eventSink: FlutterEventSink?
|
||||
private weak var registrar: FlutterPluginRegistrar?
|
||||
private var nameToId: [String: Int] = [:]
|
||||
|
||||
// MARK: - FlutterPlugin Registration
|
||||
|
||||
@@ -162,11 +163,13 @@ class MpvPlayerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, MpvPlayerD
|
||||
private func handleObserveProperty(call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||
guard let args = call.arguments as? [String: Any],
|
||||
let name = args["name"] as? String,
|
||||
let format = args["format"] as? String else {
|
||||
result(FlutterError(code: "INVALID_ARGS", message: "Missing 'name' or 'format' argument", details: nil))
|
||||
let format = args["format"] as? String,
|
||||
let id = args["id"] as? Int else {
|
||||
result(FlutterError(code: "INVALID_ARGS", message: "Missing 'name', 'format', or 'id' argument", details: nil))
|
||||
return
|
||||
}
|
||||
|
||||
nameToId[name] = id
|
||||
playerCore?.observeProperty(name, format: format)
|
||||
result(nil)
|
||||
}
|
||||
@@ -220,13 +223,9 @@ class MpvPlayerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, MpvPlayerD
|
||||
func onPropertyChange(name: String, value: Any?) {
|
||||
guard let eventSink = eventSink else { return }
|
||||
|
||||
var event: [String: Any] = ["type": "property", "name": name]
|
||||
|
||||
if let value = value {
|
||||
event["value"] = value
|
||||
if let propId = nameToId[name] {
|
||||
eventSink([propId, value as Any])
|
||||
}
|
||||
|
||||
eventSink(event)
|
||||
}
|
||||
|
||||
func onEvent(name: String, data: [String: Any]?) {
|
||||
|
||||
@@ -51,6 +51,19 @@ class PlayerAndroid extends PlayerBase {
|
||||
if (!initialized) {
|
||||
throw Exception('Failed to initialize ExoPlayer');
|
||||
}
|
||||
|
||||
// Register property observers so the plugin knows propId mappings
|
||||
await observeProperty('time-pos', 'double');
|
||||
await observeProperty('duration', 'double');
|
||||
await observeProperty('pause', 'flag');
|
||||
await observeProperty('paused-for-cache', 'flag');
|
||||
await observeProperty('track-list', 'string');
|
||||
await observeProperty('eof-reached', 'flag');
|
||||
await observeProperty('volume', 'double');
|
||||
await observeProperty('speed', 'double');
|
||||
await observeProperty('aid', 'string');
|
||||
await observeProperty('sid', 'string');
|
||||
await observeProperty('demuxer-cache-time', 'double');
|
||||
} catch (e) {
|
||||
errorController.add('Initialization failed: $e');
|
||||
rethrow;
|
||||
|
||||
@@ -37,7 +37,11 @@ abstract class PlayerBase with PlayerStreamControllersMixin implements Player {
|
||||
|
||||
StreamSubscription? _eventSubscription;
|
||||
bool _disposed = false;
|
||||
DateTime? _lastPositionEmit;
|
||||
final _throttleSw = Stopwatch()..start();
|
||||
int _lastEmitMs = 0;
|
||||
int _positionMs = 0;
|
||||
int _nextPropId = 0;
|
||||
final Map<int, String> _propIdToName = {};
|
||||
|
||||
/// Whether the player has been initialized.
|
||||
/// Subclasses should set this to true after initialization.
|
||||
@@ -90,16 +94,31 @@ abstract class PlayerBase with PlayerStreamControllersMixin implements Player {
|
||||
);
|
||||
}
|
||||
|
||||
/// Observes a property on the native player and assigns it a compact propId
|
||||
/// for efficient event channel communication.
|
||||
@protected
|
||||
Future<void> observeProperty(String name, String format) async {
|
||||
final propId = _nextPropId++;
|
||||
_propIdToName[propId] = name;
|
||||
await methodChannel.invokeMethod('observeProperty', {
|
||||
'name': name,
|
||||
'format': format,
|
||||
'id': propId,
|
||||
});
|
||||
}
|
||||
|
||||
void _handleEvent(dynamic event) {
|
||||
if (event is! Map) return;
|
||||
|
||||
final type = event['type'] as String?;
|
||||
final name = event['name'] as String?;
|
||||
|
||||
if (type == 'property' && name != null) {
|
||||
handlePropertyChange(name, event['value']);
|
||||
} else if (type == 'event' && name != null) {
|
||||
handlePlayerEvent(name, event['data'] as Map?);
|
||||
if (event is List && event.length == 2) {
|
||||
final name = _propIdToName[event[0]];
|
||||
if (name != null) {
|
||||
handlePropertyChange(name, event[1]);
|
||||
}
|
||||
} else if (event is Map) {
|
||||
final type = event['type'] as String?;
|
||||
final name = event['name'] as String?;
|
||||
if (type == 'event' && name != null) {
|
||||
handlePlayerEvent(name, event['data'] as Map?);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,14 +146,15 @@ abstract class PlayerBase with PlayerStreamControllersMixin implements Player {
|
||||
|
||||
case 'time-pos':
|
||||
if (value is num) {
|
||||
final position = Duration(milliseconds: (value * 1000).toInt());
|
||||
_state = _state.copyWith(position: position);
|
||||
// Throttle stream emissions to ~4Hz (250ms) to reduce listener/rebuild pressure.
|
||||
// _state is always updated above so synchronous reads stay current.
|
||||
final now = DateTime.now();
|
||||
if (_lastPositionEmit == null || now.difference(_lastPositionEmit!).inMilliseconds >= 250) {
|
||||
_lastPositionEmit = now;
|
||||
positionController.add(position);
|
||||
_positionMs = (value * 1000).round();
|
||||
// Only allocate Duration + copyWith + emit at ~4Hz (250ms).
|
||||
// Raw int is stored every tick so synchronous reads via _positionMs stay current.
|
||||
final nowMs = _throttleSw.elapsedMilliseconds;
|
||||
if (nowMs - _lastEmitMs >= 250) {
|
||||
_lastEmitMs = nowMs;
|
||||
final pos = Duration(milliseconds: _positionMs);
|
||||
_state = _state.copyWith(position: pos);
|
||||
positionController.add(pos);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -42,26 +42,22 @@ class PlayerNative extends PlayerBase {
|
||||
await _configureSubtitleFonts();
|
||||
|
||||
// Subscribe to MPV properties
|
||||
await _observeProperty('time-pos', 'double');
|
||||
await _observeProperty('duration', 'double');
|
||||
await _observeProperty('pause', 'flag');
|
||||
await _observeProperty('paused-for-cache', 'flag');
|
||||
await _observeProperty('track-list', (Platform.isAndroid || Platform.isWindows) ? 'string' : 'node');
|
||||
await _observeProperty('eof-reached', 'flag');
|
||||
await _observeProperty('volume', 'double');
|
||||
await _observeProperty('speed', 'double');
|
||||
await _observeProperty('aid', 'string');
|
||||
await _observeProperty('sid', 'string');
|
||||
await observeProperty('time-pos', 'double');
|
||||
await observeProperty('duration', 'double');
|
||||
await observeProperty('pause', 'flag');
|
||||
await observeProperty('paused-for-cache', 'flag');
|
||||
await observeProperty('track-list', (Platform.isAndroid || Platform.isWindows) ? 'string' : 'node');
|
||||
await observeProperty('eof-reached', 'flag');
|
||||
await observeProperty('volume', 'double');
|
||||
await observeProperty('speed', 'double');
|
||||
await observeProperty('aid', 'string');
|
||||
await observeProperty('sid', 'string');
|
||||
} catch (e) {
|
||||
errorController.add('Initialization failed: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _observeProperty(String name, String format) async {
|
||||
await methodChannel.invokeMethod('observeProperty', {'name': name, 'format': format});
|
||||
}
|
||||
|
||||
/// Configures subtitle fonts for libass support.
|
||||
/// Provides a comprehensive Unicode font (Go Noto) with CJK coverage to ensure
|
||||
/// proper rendering of non-Latin characters in subtitles.
|
||||
|
||||
@@ -249,7 +249,8 @@ std::string MpvPlayer::GetProperty(const std::string& name) {
|
||||
}
|
||||
|
||||
void MpvPlayer::ObserveProperty(const std::string& name,
|
||||
const std::string& format) {
|
||||
const std::string& format,
|
||||
int id) {
|
||||
if (disposed_ || !mpv_) return;
|
||||
|
||||
// Check if already observing.
|
||||
@@ -257,6 +258,8 @@ void MpvPlayer::ObserveProperty(const std::string& name,
|
||||
return;
|
||||
}
|
||||
|
||||
name_to_id_[name] = id;
|
||||
|
||||
mpv_format mpv_fmt = MPV_FORMAT_NONE;
|
||||
if (format == "string") {
|
||||
mpv_fmt = MPV_FORMAT_STRING;
|
||||
@@ -515,22 +518,24 @@ FlValue* MpvPlayer::NodeToFlValue(mpv_node* node) {
|
||||
}
|
||||
|
||||
void MpvPlayer::SendPropertyChange(const char* name, mpv_node* data) {
|
||||
FlValue* event_map = fl_value_new_map();
|
||||
fl_value_set_string_take(event_map, "type", fl_value_new_string("property"));
|
||||
fl_value_set_string_take(event_map, "name",
|
||||
fl_value_new_string(name ? name : ""));
|
||||
if (!name) return;
|
||||
|
||||
auto it = name_to_id_.find(name);
|
||||
if (it == name_to_id_.end()) return;
|
||||
|
||||
FlValue* list = fl_value_new_list();
|
||||
fl_value_append_take(list, fl_value_new_int(it->second));
|
||||
if (data) {
|
||||
fl_value_set_string_take(event_map, "value", NodeToFlValue(data));
|
||||
fl_value_append_take(list, NodeToFlValue(data));
|
||||
} else {
|
||||
fl_value_set_string_take(event_map, "value", fl_value_new_null());
|
||||
fl_value_append_take(list, fl_value_new_null());
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(callback_mutex_);
|
||||
if (event_callback_) {
|
||||
event_callback_(event_map);
|
||||
event_callback_(list);
|
||||
}
|
||||
fl_value_unref(event_map);
|
||||
fl_value_unref(list);
|
||||
}
|
||||
|
||||
void MpvPlayer::SendEvent(const std::string& name, FlValue* data) {
|
||||
|
||||
@@ -71,7 +71,9 @@ class MpvPlayer {
|
||||
/// Changes will be reported via the event callback.
|
||||
/// @param name Property name to observe.
|
||||
/// @param format Format type ("string", "flag", "int64", "double", "node").
|
||||
void ObserveProperty(const std::string& name, const std::string& format);
|
||||
/// @param id Property ID assigned by Dart for compact event encoding.
|
||||
void ObserveProperty(const std::string& name, const std::string& format,
|
||||
int id);
|
||||
|
||||
/// Renders a frame to the current OpenGL context.
|
||||
/// Must be called from the GTK render callback.
|
||||
@@ -133,6 +135,7 @@ class MpvPlayer {
|
||||
|
||||
uint64_t next_reply_userdata_ = 1;
|
||||
std::map<std::string, uint64_t> observed_properties_;
|
||||
std::map<std::string, int> name_to_id_;
|
||||
|
||||
// Pending async commands: request_id -> callback
|
||||
std::map<uint64_t, CommandCallback> pending_commands_;
|
||||
|
||||
@@ -411,6 +411,7 @@ static void mpv_plugin_handle_method_call(FlMethodChannel* channel,
|
||||
} else {
|
||||
FlValue* name_value = fl_value_lookup_string(args, "name");
|
||||
FlValue* format_value = fl_value_lookup_string(args, "format");
|
||||
FlValue* id_value = fl_value_lookup_string(args, "id");
|
||||
|
||||
if (name_value == nullptr ||
|
||||
fl_value_get_type(name_value) != FL_VALUE_TYPE_STRING) {
|
||||
@@ -420,9 +421,14 @@ static void mpv_plugin_handle_method_call(FlMethodChannel* channel,
|
||||
fl_value_get_type(format_value) != FL_VALUE_TYPE_STRING) {
|
||||
response = FL_METHOD_RESPONSE(fl_method_error_response_new(
|
||||
"INVALID_ARGS", "Missing 'format'", nullptr));
|
||||
} else if (id_value == nullptr ||
|
||||
fl_value_get_type(id_value) != FL_VALUE_TYPE_INT) {
|
||||
response = FL_METHOD_RESPONSE(fl_method_error_response_new(
|
||||
"INVALID_ARGS", "Missing 'id'", nullptr));
|
||||
} else {
|
||||
self->player->ObserveProperty(fl_value_get_string(name_value),
|
||||
fl_value_get_string(format_value));
|
||||
fl_value_get_string(format_value),
|
||||
static_cast<int>(fl_value_get_int(id_value)));
|
||||
response = FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,11 +98,48 @@ class MpvPlayerCore: NSObject {
|
||||
return false
|
||||
}
|
||||
|
||||
// Register for fullscreen notifications to avoid MoltenVK swapchain crash
|
||||
let nc = NotificationCenter.default
|
||||
nc.addObserver(self, selector: #selector(windowWillEnterFullScreen),
|
||||
name: NSWindow.willEnterFullScreenNotification, object: window)
|
||||
nc.addObserver(self, selector: #selector(windowDidEnterFullScreen),
|
||||
name: NSWindow.didEnterFullScreenNotification, object: window)
|
||||
nc.addObserver(self, selector: #selector(windowWillExitFullScreen),
|
||||
name: NSWindow.willExitFullScreenNotification, object: window)
|
||||
nc.addObserver(self, selector: #selector(windowDidExitFullScreen),
|
||||
name: NSWindow.didExitFullScreenNotification, object: window)
|
||||
|
||||
isInitialized = true
|
||||
print("[MpvPlayerCore] Initialized successfully with MPV")
|
||||
return true
|
||||
}
|
||||
|
||||
// MARK: - Fullscreen Transition Handling
|
||||
|
||||
@objc private func windowWillEnterFullScreen(_ notification: Notification) {
|
||||
guard mpv != nil else { return }
|
||||
print("[MpvPlayerCore] willEnterFullScreen — disabling video output")
|
||||
mpv_set_property_string(mpv, "vid", "no")
|
||||
}
|
||||
|
||||
@objc private func windowDidEnterFullScreen(_ notification: Notification) {
|
||||
guard mpv != nil else { return }
|
||||
print("[MpvPlayerCore] didEnterFullScreen — re-enabling video output")
|
||||
mpv_set_property_string(mpv, "vid", "auto")
|
||||
}
|
||||
|
||||
@objc private func windowWillExitFullScreen(_ notification: Notification) {
|
||||
guard mpv != nil else { return }
|
||||
print("[MpvPlayerCore] willExitFullScreen — disabling video output")
|
||||
mpv_set_property_string(mpv, "vid", "no")
|
||||
}
|
||||
|
||||
@objc private func windowDidExitFullScreen(_ notification: Notification) {
|
||||
guard mpv != nil else { return }
|
||||
print("[MpvPlayerCore] didExitFullScreen — re-enabling video output")
|
||||
mpv_set_property_string(mpv, "vid", "auto")
|
||||
}
|
||||
|
||||
private func setupMpv() -> Bool {
|
||||
guard let metalLayer = metalLayer else { return false }
|
||||
|
||||
@@ -508,6 +545,8 @@ class MpvPlayerCore: NSObject {
|
||||
// MARK: - Cleanup
|
||||
|
||||
func dispose() {
|
||||
NotificationCenter.default.removeObserver(self)
|
||||
|
||||
// Cancel any pending async commands
|
||||
pendingCommandsLock.lock()
|
||||
let pending = pendingCommands
|
||||
|
||||
@@ -9,6 +9,7 @@ class MpvPlayerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, MpvPlayerD
|
||||
private var playerCore: MpvPlayerCore?
|
||||
private var eventSink: FlutterEventSink?
|
||||
private weak var registrar: FlutterPluginRegistrar?
|
||||
private var nameToId: [String: Int] = [:]
|
||||
|
||||
// MARK: - FlutterPlugin Registration
|
||||
|
||||
@@ -162,11 +163,13 @@ class MpvPlayerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, MpvPlayerD
|
||||
private func handleObserveProperty(call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||
guard let args = call.arguments as? [String: Any],
|
||||
let name = args["name"] as? String,
|
||||
let format = args["format"] as? String else {
|
||||
result(FlutterError(code: "INVALID_ARGS", message: "Missing 'name' or 'format' argument", details: nil))
|
||||
let format = args["format"] as? String,
|
||||
let id = args["id"] as? Int else {
|
||||
result(FlutterError(code: "INVALID_ARGS", message: "Missing 'name', 'format', or 'id' argument", details: nil))
|
||||
return
|
||||
}
|
||||
|
||||
nameToId[name] = id
|
||||
playerCore?.observeProperty(name, format: format)
|
||||
result(nil)
|
||||
}
|
||||
@@ -220,13 +223,9 @@ class MpvPlayerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, MpvPlayerD
|
||||
func onPropertyChange(name: String, value: Any?) {
|
||||
guard let eventSink = eventSink else { return }
|
||||
|
||||
var event: [String: Any] = ["type": "property", "name": name]
|
||||
|
||||
if let value = value {
|
||||
event["value"] = value
|
||||
if let propId = nameToId[name] {
|
||||
eventSink([propId, value as Any])
|
||||
}
|
||||
|
||||
eventSink(event)
|
||||
}
|
||||
|
||||
func onEvent(name: String, data: [String: Any]?) {
|
||||
|
||||
@@ -212,7 +212,8 @@ std::string MpvPlayer::GetProperty(const std::string& name) {
|
||||
}
|
||||
|
||||
void MpvPlayer::ObserveProperty(const std::string& name,
|
||||
const std::string& format) {
|
||||
const std::string& format,
|
||||
int id) {
|
||||
if (!mpv_) return;
|
||||
|
||||
// Check if already observing.
|
||||
@@ -220,6 +221,8 @@ void MpvPlayer::ObserveProperty(const std::string& name,
|
||||
return;
|
||||
}
|
||||
|
||||
name_to_id_[name] = id;
|
||||
|
||||
mpv_format mpv_fmt = MPV_FORMAT_NONE;
|
||||
if (format == "string") {
|
||||
mpv_fmt = MPV_FORMAT_STRING;
|
||||
@@ -418,11 +421,10 @@ void MpvPlayer::HandleMpvEvent(mpv_event* event) {
|
||||
}
|
||||
|
||||
void MpvPlayer::SendPropertyChange(const char* name, mpv_node* data) {
|
||||
flutter::EncodableMap event;
|
||||
event[flutter::EncodableValue("type")] =
|
||||
flutter::EncodableValue("property");
|
||||
event[flutter::EncodableValue("name")] =
|
||||
flutter::EncodableValue(name ? name : "");
|
||||
if (!name) return;
|
||||
|
||||
auto it = name_to_id_.find(name);
|
||||
if (it == name_to_id_.end()) return;
|
||||
|
||||
flutter::EncodableValue value;
|
||||
if (data) {
|
||||
@@ -445,11 +447,14 @@ void MpvPlayer::SendPropertyChange(const char* name, mpv_node* data) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
event[flutter::EncodableValue("value")] = value;
|
||||
|
||||
flutter::EncodableList list;
|
||||
list.push_back(flutter::EncodableValue(it->second));
|
||||
list.push_back(value);
|
||||
|
||||
std::lock_guard<std::mutex> lock(callback_mutex_);
|
||||
if (event_callback_) {
|
||||
event_callback_(event);
|
||||
event_callback_(flutter::EncodableValue(list));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -464,7 +469,7 @@ void MpvPlayer::SendEvent(const std::string& name,
|
||||
|
||||
std::lock_guard<std::mutex> lock(callback_mutex_);
|
||||
if (event_callback_) {
|
||||
event_callback_(event);
|
||||
event_callback_(flutter::EncodableValue(event));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace mpv {
|
||||
class MpvPlayer {
|
||||
public:
|
||||
using EventCallback =
|
||||
std::function<void(const flutter::EncodableMap&)>;
|
||||
std::function<void(const flutter::EncodableValue&)>;
|
||||
|
||||
MpvPlayer();
|
||||
~MpvPlayer();
|
||||
@@ -53,7 +53,8 @@ class MpvPlayer {
|
||||
std::string GetProperty(const std::string& name);
|
||||
|
||||
// Observes an mpv property for changes.
|
||||
void ObserveProperty(const std::string& name, const std::string& format);
|
||||
void ObserveProperty(const std::string& name, const std::string& format,
|
||||
int id);
|
||||
|
||||
// Returns the mpv video window handle.
|
||||
HWND GetHwnd() const { return hwnd_; }
|
||||
@@ -90,6 +91,7 @@ class MpvPlayer {
|
||||
|
||||
uint64_t next_reply_userdata_ = 1;
|
||||
std::map<std::string, uint64_t> observed_properties_;
|
||||
std::map<std::string, int> name_to_id_;
|
||||
|
||||
// Pending async commands: request_id -> callback
|
||||
std::map<uint64_t, CommandCallback> pending_commands_;
|
||||
|
||||
@@ -150,7 +150,7 @@ void MpvPlayerPlugin::HandleMethodCall(
|
||||
if (success) {
|
||||
LogToFile("MPV Plugin: Player initialized successfully");
|
||||
// Set up event callback.
|
||||
player_->SetEventCallback([this](const flutter::EncodableMap& event) {
|
||||
player_->SetEventCallback([this](const flutter::EncodableValue& event) {
|
||||
SendEvent(event);
|
||||
});
|
||||
|
||||
@@ -291,6 +291,7 @@ void MpvPlayerPlugin::HandleMethodCall(
|
||||
const auto& map = std::get<flutter::EncodableMap>(*args);
|
||||
auto name_it = map.find(flutter::EncodableValue("name"));
|
||||
auto format_it = map.find(flutter::EncodableValue("format"));
|
||||
auto id_it = map.find(flutter::EncodableValue("id"));
|
||||
|
||||
if (name_it == map.end() ||
|
||||
!std::holds_alternative<std::string>(name_it->second)) {
|
||||
@@ -302,9 +303,15 @@ void MpvPlayerPlugin::HandleMethodCall(
|
||||
result->Error("INVALID_ARGS", "Missing 'format'");
|
||||
return;
|
||||
}
|
||||
if (id_it == map.end() ||
|
||||
!std::holds_alternative<int32_t>(id_it->second)) {
|
||||
result->Error("INVALID_ARGS", "Missing 'id'");
|
||||
return;
|
||||
}
|
||||
|
||||
player_->ObserveProperty(std::get<std::string>(name_it->second),
|
||||
std::get<std::string>(format_it->second));
|
||||
std::get<std::string>(format_it->second),
|
||||
std::get<int32_t>(id_it->second));
|
||||
result->Success();
|
||||
} else if (method == "setVisible") {
|
||||
const auto* args = method_call.arguments();
|
||||
@@ -382,9 +389,9 @@ void MpvPlayerPlugin::HandleMethodCall(
|
||||
}
|
||||
}
|
||||
|
||||
void MpvPlayerPlugin::SendEvent(const flutter::EncodableMap& event) {
|
||||
void MpvPlayerPlugin::SendEvent(const flutter::EncodableValue& event) {
|
||||
if (event_sink_) {
|
||||
event_sink_->Success(flutter::EncodableValue(event));
|
||||
event_sink_->Success(event);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ class MpvPlayerPlugin : public flutter::Plugin {
|
||||
const flutter::MethodCall<flutter::EncodableValue>& method_call,
|
||||
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result);
|
||||
|
||||
void SendEvent(const flutter::EncodableMap& event);
|
||||
void SendEvent(const flutter::EncodableValue& event);
|
||||
|
||||
HWND GetWindow();
|
||||
HWND GetChildWindow();
|
||||
|
||||
Reference in New Issue
Block a user