530 lines
18 KiB
C#
530 lines
18 KiB
C#
|
|
using System.Runtime.InteropServices;
|
|
|
|
using static MpvNet.Native.LibMpv;
|
|
|
|
namespace MpvNet;
|
|
|
|
public class MpvClient
|
|
{
|
|
public event Action<string[]>? ClientMessage; // client-message MPV_EVENT_CLIENT_MESSAGE
|
|
public event Action<mpv_log_level, string>? LogMessage; // log-message MPV_EVENT_LOG_MESSAGE
|
|
public event Action<mpv_end_file_reason>? EndFile; // end-file MPV_EVENT_END_FILE
|
|
public event Action? Shutdown; // shutdown MPV_EVENT_SHUTDOWN
|
|
public event Action? GetPropertyReply; // get-property-reply MPV_EVENT_GET_PROPERTY_REPLY
|
|
public event Action? SetPropertyReply; // set-property-reply MPV_EVENT_SET_PROPERTY_REPLY
|
|
public event Action? CommandReply; // command-reply MPV_EVENT_COMMAND_REPLY
|
|
public event Action? StartFile; // start-file MPV_EVENT_START_FILE
|
|
public event Action? FileLoaded; // file-loaded MPV_EVENT_FILE_LOADED
|
|
public event Action? VideoReconfig; // video-reconfig MPV_EVENT_VIDEO_RECONFIG
|
|
public event Action? AudioReconfig; // audio-reconfig MPV_EVENT_AUDIO_RECONFIG
|
|
public event Action? Seek; // seek MPV_EVENT_SEEK
|
|
public event Action? PlaybackRestart; // playback-restart MPV_EVENT_PLAYBACK_RESTART
|
|
|
|
public Dictionary<string, List<Action>> PropChangeActions { get; set; } = [];
|
|
public Dictionary<string, List<Action<int>>> IntPropChangeActions { get; set; } = [];
|
|
public Dictionary<string, List<Action<bool>>> BoolPropChangeActions { get; set; } = [];
|
|
public Dictionary<string, List<Action<double>>> DoublePropChangeActions { get; set; } = [];
|
|
public Dictionary<string, List<Action<string>>> StringPropChangeActions { get; set; } = [];
|
|
|
|
public nint Handle { get; set; }
|
|
|
|
public void EventLoop()
|
|
{
|
|
while (true)
|
|
{
|
|
IntPtr ptr = mpv_wait_event(Handle, -1);
|
|
mpv_event evt = (mpv_event)Marshal.PtrToStructure(ptr, typeof(mpv_event))!;
|
|
|
|
try
|
|
{
|
|
switch (evt.event_id)
|
|
{
|
|
case mpv_event_id.MPV_EVENT_SHUTDOWN:
|
|
OnShutdown();
|
|
return;
|
|
case mpv_event_id.MPV_EVENT_LOG_MESSAGE:
|
|
{
|
|
var data = (mpv_event_log_message)Marshal.PtrToStructure(evt.data, typeof(mpv_event_log_message))!;
|
|
OnLogMessage(data);
|
|
}
|
|
break;
|
|
case mpv_event_id.MPV_EVENT_CLIENT_MESSAGE:
|
|
{
|
|
var data = (mpv_event_client_message)Marshal.PtrToStructure(evt.data, typeof(mpv_event_client_message))!;
|
|
OnClientMessage(data);
|
|
}
|
|
break;
|
|
case mpv_event_id.MPV_EVENT_VIDEO_RECONFIG:
|
|
OnVideoReconfig();
|
|
break;
|
|
case mpv_event_id.MPV_EVENT_END_FILE:
|
|
{
|
|
var data = (mpv_event_end_file)Marshal.PtrToStructure(evt.data, typeof(mpv_event_end_file))!;
|
|
OnEndFile(data);
|
|
}
|
|
break;
|
|
case mpv_event_id.MPV_EVENT_FILE_LOADED: // triggered after MPV_EVENT_START_FILE
|
|
OnFileLoaded();
|
|
break;
|
|
case mpv_event_id.MPV_EVENT_PROPERTY_CHANGE:
|
|
{
|
|
var data = (mpv_event_property)Marshal.PtrToStructure(evt.data, typeof(mpv_event_property))!;
|
|
OnPropertyChange(data);
|
|
}
|
|
break;
|
|
case mpv_event_id.MPV_EVENT_GET_PROPERTY_REPLY:
|
|
OnGetPropertyReply();
|
|
break;
|
|
case mpv_event_id.MPV_EVENT_SET_PROPERTY_REPLY:
|
|
OnSetPropertyReply();
|
|
break;
|
|
case mpv_event_id.MPV_EVENT_COMMAND_REPLY:
|
|
OnCommandReply();
|
|
break;
|
|
case mpv_event_id.MPV_EVENT_START_FILE: // triggered before MPV_EVENT_FILE_LOADED
|
|
OnStartFile();
|
|
break;
|
|
case mpv_event_id.MPV_EVENT_AUDIO_RECONFIG:
|
|
OnAudioReconfig();
|
|
break;
|
|
case mpv_event_id.MPV_EVENT_SEEK:
|
|
OnSeek();
|
|
break;
|
|
case mpv_event_id.MPV_EVENT_PLAYBACK_RESTART:
|
|
OnPlaybackRestart();
|
|
break;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Terminal.WriteError(ex);
|
|
}
|
|
}
|
|
}
|
|
|
|
protected virtual void OnClientMessage(mpv_event_client_message data) =>
|
|
ClientMessage?.Invoke(ConvertFromUtf8Strings(data.args, data.num_args));
|
|
|
|
protected virtual void OnLogMessage(mpv_event_log_message data)
|
|
{
|
|
if (LogMessage != null)
|
|
{
|
|
string msg = $"[{ConvertFromUtf8(data.prefix)}] {ConvertFromUtf8(data.text)}";
|
|
LogMessage.Invoke(data.log_level, msg);
|
|
}
|
|
}
|
|
|
|
protected virtual void OnPropertyChange(mpv_event_property data)
|
|
{
|
|
if (data.format == mpv_format.MPV_FORMAT_FLAG)
|
|
{
|
|
lock (BoolPropChangeActions)
|
|
foreach (var pair in BoolPropChangeActions)
|
|
if (pair.Key == data.name)
|
|
{
|
|
bool value = Marshal.PtrToStructure<int>(data.data) == 1;
|
|
|
|
foreach (var action in pair.Value)
|
|
action.Invoke(value);
|
|
}
|
|
}
|
|
else if (data.format == mpv_format.MPV_FORMAT_STRING)
|
|
{
|
|
lock (StringPropChangeActions)
|
|
{
|
|
foreach (var pair in StringPropChangeActions)
|
|
{
|
|
if (pair.Key == data.name)
|
|
{
|
|
string value = ConvertFromUtf8(Marshal.PtrToStructure<IntPtr>(data.data));
|
|
|
|
foreach (var action in pair.Value)
|
|
{
|
|
action.Invoke(value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (data.format == mpv_format.MPV_FORMAT_INT64)
|
|
{
|
|
lock (IntPropChangeActions)
|
|
{
|
|
foreach (var pair in IntPropChangeActions)
|
|
{
|
|
if (pair.Key == data.name)
|
|
{
|
|
int value = Marshal.PtrToStructure<int>(data.data);
|
|
|
|
foreach (var action in pair.Value)
|
|
{
|
|
action.Invoke(value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (data.format == mpv_format.MPV_FORMAT_NONE)
|
|
{
|
|
lock (PropChangeActions)
|
|
{
|
|
foreach (var pair in PropChangeActions)
|
|
{
|
|
if (pair.Key == data.name)
|
|
{
|
|
foreach (var action in pair.Value)
|
|
{
|
|
action.Invoke();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (data.format == mpv_format.MPV_FORMAT_DOUBLE)
|
|
{
|
|
lock (DoublePropChangeActions)
|
|
{
|
|
foreach (var pair in DoublePropChangeActions)
|
|
{
|
|
if (pair.Key == data.name)
|
|
{
|
|
double value = Marshal.PtrToStructure<double>(data.data);
|
|
|
|
foreach (var action in pair.Value)
|
|
{
|
|
action.Invoke(value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
protected virtual void OnEndFile(mpv_event_end_file data) => EndFile?.Invoke((mpv_end_file_reason)data.reason);
|
|
protected virtual void OnFileLoaded() => FileLoaded?.Invoke();
|
|
protected virtual void OnShutdown() => Shutdown?.Invoke();
|
|
protected virtual void OnGetPropertyReply() => GetPropertyReply?.Invoke();
|
|
protected virtual void OnSetPropertyReply() => SetPropertyReply?.Invoke();
|
|
protected virtual void OnCommandReply() => CommandReply?.Invoke();
|
|
protected virtual void OnStartFile() => StartFile?.Invoke();
|
|
protected virtual void OnVideoReconfig() => VideoReconfig?.Invoke();
|
|
protected virtual void OnAudioReconfig() => AudioReconfig?.Invoke();
|
|
protected virtual void OnSeek() => Seek?.Invoke();
|
|
protected virtual void OnPlaybackRestart() => PlaybackRestart?.Invoke();
|
|
|
|
public void Command(string command)
|
|
{
|
|
mpv_error err = mpv_command_string(Handle, command);
|
|
|
|
if (err < 0)
|
|
HandleError(err, "error executing command: " + command);
|
|
}
|
|
|
|
public void CommandV(params string[] args)
|
|
{
|
|
int count = args.Length + 1;
|
|
IntPtr[] pointers = new IntPtr[count];
|
|
IntPtr rootPtr = Marshal.AllocHGlobal(IntPtr.Size * count);
|
|
|
|
for (int index = 0; index < args.Length; index++)
|
|
{
|
|
var bytes = GetUtf8Bytes(args[index]);
|
|
IntPtr ptr = Marshal.AllocHGlobal(bytes.Length);
|
|
Marshal.Copy(bytes, 0, ptr, bytes.Length);
|
|
pointers[index] = ptr;
|
|
}
|
|
|
|
Marshal.Copy(pointers, 0, rootPtr, count);
|
|
mpv_error err = mpv_command(Handle, rootPtr);
|
|
|
|
foreach (IntPtr ptr in pointers)
|
|
Marshal.FreeHGlobal(ptr);
|
|
|
|
Marshal.FreeHGlobal(rootPtr);
|
|
|
|
if (err < 0)
|
|
HandleError(err, "error executing command: " + string.Join("\n", args));
|
|
}
|
|
|
|
public string Expand(string? value)
|
|
{
|
|
if (value == null)
|
|
return "";
|
|
|
|
if (!value.Contains("${"))
|
|
return value;
|
|
|
|
string[] args = { "expand-text", value };
|
|
int count = args.Length + 1;
|
|
IntPtr[] pointers = new IntPtr[count];
|
|
IntPtr rootPtr = Marshal.AllocHGlobal(IntPtr.Size * count);
|
|
|
|
for (int index = 0; index < args.Length; index++)
|
|
{
|
|
var bytes = GetUtf8Bytes(args[index]);
|
|
IntPtr ptr = Marshal.AllocHGlobal(bytes.Length);
|
|
Marshal.Copy(bytes, 0, ptr, bytes.Length);
|
|
pointers[index] = ptr;
|
|
}
|
|
|
|
Marshal.Copy(pointers, 0, rootPtr, count);
|
|
IntPtr resultNodePtr = Marshal.AllocHGlobal(16);
|
|
mpv_error err = mpv_command_ret(Handle, rootPtr, resultNodePtr);
|
|
|
|
foreach (IntPtr ptr in pointers)
|
|
{
|
|
Marshal.FreeHGlobal(ptr);
|
|
}
|
|
|
|
Marshal.FreeHGlobal(rootPtr);
|
|
|
|
if (err < 0)
|
|
{
|
|
HandleError(err, "error executing command: " + string.Join("\n", args));
|
|
Marshal.FreeHGlobal(resultNodePtr);
|
|
return "property expansion error";
|
|
}
|
|
|
|
mpv_node resultNode = Marshal.PtrToStructure<mpv_node>(resultNodePtr);
|
|
string ret = ConvertFromUtf8(resultNode.str);
|
|
mpv_free_node_contents(resultNodePtr);
|
|
Marshal.FreeHGlobal(resultNodePtr);
|
|
return ret;
|
|
}
|
|
|
|
public bool GetPropertyBool(string name)
|
|
{
|
|
mpv_error err = mpv_get_property(Handle, GetUtf8Bytes(name),
|
|
mpv_format.MPV_FORMAT_FLAG, out IntPtr lpBuffer);
|
|
|
|
if (err < 0)
|
|
HandleError(err, "error getting property: " + name);
|
|
|
|
return lpBuffer.ToInt32() != 0;
|
|
}
|
|
|
|
public void SetPropertyBool(string name, bool value)
|
|
{
|
|
long val = value ? 1 : 0;
|
|
mpv_error err = mpv_set_property(Handle, GetUtf8Bytes(name), mpv_format.MPV_FORMAT_FLAG, ref val);
|
|
|
|
if (err < 0)
|
|
HandleError(err, $"error setting property: {name} = {value}");
|
|
}
|
|
|
|
public int GetPropertyInt(string name)
|
|
{
|
|
mpv_error err = mpv_get_property(Handle, GetUtf8Bytes(name),
|
|
mpv_format.MPV_FORMAT_INT64, out IntPtr lpBuffer);
|
|
|
|
if (err < 0 && App.DebugMode)
|
|
HandleError(err, "error getting property: " + name);
|
|
|
|
return lpBuffer.ToInt32();
|
|
}
|
|
|
|
public void SetPropertyInt(string name, int value)
|
|
{
|
|
long val = value;
|
|
mpv_error err = mpv_set_property(Handle, GetUtf8Bytes(name), mpv_format.MPV_FORMAT_INT64, ref val);
|
|
|
|
if (err < 0)
|
|
HandleError(err, $"error setting property: {name} = {value}");
|
|
}
|
|
|
|
public void SetPropertyLong(string name, long value)
|
|
{
|
|
mpv_error err = mpv_set_property(Handle, GetUtf8Bytes(name), mpv_format.MPV_FORMAT_INT64, ref value);
|
|
|
|
if (err < 0)
|
|
HandleError(err, $"error setting property: {name} = {value}");
|
|
}
|
|
|
|
public long GetPropertyLong(string name)
|
|
{
|
|
mpv_error err = mpv_get_property(Handle, GetUtf8Bytes(name),
|
|
mpv_format.MPV_FORMAT_INT64, out IntPtr lpBuffer);
|
|
|
|
if (err < 0)
|
|
HandleError(err, "error getting property: " + name);
|
|
|
|
return lpBuffer.ToInt64();
|
|
}
|
|
|
|
public double GetPropertyDouble(string name, bool handleError = true)
|
|
{
|
|
mpv_error err = mpv_get_property(Handle, GetUtf8Bytes(name),
|
|
mpv_format.MPV_FORMAT_DOUBLE, out double value);
|
|
|
|
if (err < 0 && handleError && App.DebugMode)
|
|
HandleError(err, "error getting property: " + name);
|
|
|
|
return value;
|
|
}
|
|
|
|
public void SetPropertyDouble(string name, double value)
|
|
{
|
|
double val = value;
|
|
mpv_error err = mpv_set_property(Handle, GetUtf8Bytes(name), mpv_format.MPV_FORMAT_DOUBLE, ref val);
|
|
|
|
if (err < 0)
|
|
HandleError(err, $"error setting property: {name} = {value}");
|
|
}
|
|
|
|
public string GetPropertyString(string name)
|
|
{
|
|
if (Handle == IntPtr.Zero)
|
|
return "";
|
|
|
|
mpv_error err = mpv_get_property(Handle, GetUtf8Bytes(name),
|
|
mpv_format.MPV_FORMAT_STRING, out IntPtr lpBuffer);
|
|
|
|
if (err == 0)
|
|
{
|
|
string ret = ConvertFromUtf8(lpBuffer);
|
|
mpv_free(lpBuffer);
|
|
return ret;
|
|
}
|
|
|
|
if (err < 0 && App.DebugMode)
|
|
HandleError(err, "error getting property: " + name);
|
|
|
|
return "";
|
|
}
|
|
|
|
public void SetPropertyString(string name, string value)
|
|
{
|
|
if (Handle == IntPtr.Zero)
|
|
{
|
|
Terminal.WriteError($"error setting property: {name} = {value}");
|
|
return;
|
|
}
|
|
|
|
byte[] bytes = GetUtf8Bytes(value);
|
|
mpv_error err = mpv_set_property(Handle, GetUtf8Bytes(name), mpv_format.MPV_FORMAT_STRING, ref bytes);
|
|
|
|
if (err < 0)
|
|
HandleError(err, $"error setting property: {name} = {value}");
|
|
}
|
|
|
|
public string GetPropertyOsdString(string name)
|
|
{
|
|
mpv_error err = mpv_get_property(Handle, GetUtf8Bytes(name),
|
|
mpv_format.MPV_FORMAT_OSD_STRING, out IntPtr lpBuffer);
|
|
|
|
if (err == 0)
|
|
{
|
|
string ret = ConvertFromUtf8(lpBuffer);
|
|
mpv_free(lpBuffer);
|
|
return ret;
|
|
}
|
|
|
|
if (err < 0)
|
|
HandleError(err, "error getting property: " + name);
|
|
|
|
return "";
|
|
}
|
|
|
|
public void ObservePropertyInt(string name, Action<int> action)
|
|
{
|
|
lock (IntPropChangeActions)
|
|
{
|
|
if (!IntPropChangeActions.ContainsKey(name))
|
|
{
|
|
mpv_error err = mpv_observe_property(Handle, 0, name, mpv_format.MPV_FORMAT_INT64);
|
|
|
|
if (err < 0)
|
|
HandleError(err, "error observing property: " + name);
|
|
else
|
|
IntPropChangeActions[name] = [];
|
|
}
|
|
|
|
if (IntPropChangeActions.ContainsKey(name))
|
|
IntPropChangeActions[name].Add(action);
|
|
}
|
|
}
|
|
|
|
public void ObservePropertyDouble(string name, Action<double> action)
|
|
{
|
|
lock (DoublePropChangeActions)
|
|
{
|
|
if (!DoublePropChangeActions.ContainsKey(name))
|
|
{
|
|
mpv_error err = mpv_observe_property(Handle, 0, name, mpv_format.MPV_FORMAT_DOUBLE);
|
|
|
|
if (err < 0)
|
|
HandleError(err, "error observing property: " + name);
|
|
else
|
|
DoublePropChangeActions[name] = [];
|
|
}
|
|
|
|
if (DoublePropChangeActions.ContainsKey(name))
|
|
DoublePropChangeActions[name].Add(action);
|
|
}
|
|
}
|
|
|
|
public void ObservePropertyBool(string name, Action<bool> action)
|
|
{
|
|
lock (BoolPropChangeActions)
|
|
{
|
|
if (!BoolPropChangeActions.ContainsKey(name))
|
|
{
|
|
mpv_error err = mpv_observe_property(Handle, 0, name, mpv_format.MPV_FORMAT_FLAG);
|
|
|
|
if (err < 0)
|
|
HandleError(err, "error observing property: " + name);
|
|
else
|
|
BoolPropChangeActions[name] = [];
|
|
}
|
|
|
|
if (BoolPropChangeActions.ContainsKey(name))
|
|
BoolPropChangeActions[name].Add(action);
|
|
}
|
|
}
|
|
|
|
public void ObservePropertyString(string name, Action<string> action)
|
|
{
|
|
lock (StringPropChangeActions)
|
|
{
|
|
if (!StringPropChangeActions.ContainsKey(name))
|
|
{
|
|
mpv_error err = mpv_observe_property(Handle, 0, name, mpv_format.MPV_FORMAT_STRING);
|
|
|
|
if (err < 0)
|
|
HandleError(err, "error observing property: " + name);
|
|
else
|
|
StringPropChangeActions[name] = [];
|
|
}
|
|
|
|
if (StringPropChangeActions.ContainsKey(name))
|
|
StringPropChangeActions[name].Add(action);
|
|
}
|
|
}
|
|
|
|
public void ObserveProperty(string name, Action action)
|
|
{
|
|
lock (PropChangeActions)
|
|
{
|
|
if (!PropChangeActions.ContainsKey(name))
|
|
{
|
|
mpv_error err = mpv_observe_property(Handle, 0, name, mpv_format.MPV_FORMAT_NONE);
|
|
|
|
if (err < 0)
|
|
HandleError(err, "error observing property: " + name);
|
|
else
|
|
PropChangeActions[name] = [];
|
|
}
|
|
|
|
if (PropChangeActions.ContainsKey(name))
|
|
PropChangeActions[name].Add(action);
|
|
}
|
|
}
|
|
|
|
static void HandleError(mpv_error err, string msg)
|
|
{
|
|
Terminal.WriteError(msg);
|
|
Terminal.WriteError(GetError(err));
|
|
}
|
|
}
|