Files
mpv.net/src/MpvNet/MpvClient.cs
2025-01-11 14:05:54 -05:00

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));
}
}