Hi, since I bought my new Coyote E-Stim device, I’ve been looking for the best way to sync it with funscripts. For the longest time, I’ve been using the Coyote GUI with ScriptPlayer. While that works for the most part, I often experience crashes and I don’t like how the Coyote GUI still sends a signal when there is no action on the funscript.
Some time ago I found Howl, a Android based app that since 2 versions ago has funscript support via a Kodi plugin.
While that does the job as well, I don’t really like Kodi as my funscipt video library.
I’d like to ask if anyone here’d be able to write a plugin for MFP to connect it with Howl, maybe using the Kodi plugin as a base? Since my coding skills are basically non-existant, I hope someone from this community would be able to help out with this.
Someone actually recently wrote a Howl plugin using AI.
I simplified it a little bit and did some touchups, don’t know if it works.
Requires patreon version of MFP.
Save to “Plugins/HowlSync.cs”, edit in notepad and change HowlEndpoint to your IP address.
using System.Net.Http;
using System.Text;
public class HowlSyncPlugin : PluginBase
{
private double _position = double.NaN;
private double _lastPosition = double.NaN;
private HttpClient _client;
public string HowlEndpoint = "192.168.1.100";
public string SourceAxis = "L0";
protected override void OnInitialize() => _client = NetUtils.CreateHttpClient();
protected override void OnDispose() => _client.Dispose();
protected override void HandleMessage(ScriptChangedMessage message)
{
if (!DeviceAxis.TryParse(SourceAxis, out var axis))
return;
if (message.Axis != axis)
return;
var funscript = Funscript.FromKeyframes(message.Script?.Keyframes);
var funscriptJson = JsonConvert.SerializeObject(funscript, Formatting.None);
SendFunscript(message.Script?.Name, funscriptJson);
}
protected override void HandleMessage(MediaPlayingChangedMessage message)
{
if (message.IsPlaying)
SendStartPlayer(double.IsFinite(_position) ? _position : 0);
else
SendStopPlayer();
}
protected override void HandleMessage(MediaPositionChangedMessage message)
{
_position = message.Position.TotalSeconds;
if (!double.IsFinite(_position))
return;
if (double.IsFinite(_lastPosition) && Math.Abs(_position - _lastPosition) < 0.5)
return;
_lastPosition = _position;
SendSeek(_position);
}
private void SendFunscript(string title, string funscriptJson) => Send("load_funscript", $@"{{ ""title"": ""{title}"", ""funscript"": ""{funscriptJson}"" }}");
private void SendStartPlayer(double from) => Send("start_player", $@"{{ ""from"": {from} }}");
private void SendSeek(double position) => Send("seek", $@"{{ ""position"": {position} }}");
private void SendStopPlayer() => Send("stop_player", null);
private void Send(string route, string content)
=> Task.Run(() => _client.PostAsync($"http://{HowlEndpoint}:4695/{route}", content != null ? new StringContent(content, Encoding.UTF8, "application/json") : null, CancellationToken));
}
public record FunscriptAction(int at, int value)
{
public FunscriptAction(Keyframe keyframe)
: this((int)Math.Round(keyframe.Position * 1000), (int)Math.Round(keyframe.Value * 100)) { }
}
public record Funscript(List<FunscriptAction> actions)
{
public static Funscript FromKeyframes(KeyframeCollection keyframes)
=> keyframes == null ? new([]) : new([.. keyframes.Select(k => new FunscriptAction(k))]);
}
Thank you!
This does work, as it connects with Howl, however, the content does not sync up past the inital loading. Pausing or skipping ahead in the video does nothing to the funscript playback (neiter does closing the video player or MFP), which does work with the Kodi plugin. Any chance you or someone else could get that to work?
Still does not work, but if you’re willing, you can actually test it if you have access to any android phone, because the dev was clever enough to implement every feature in Howl in a way that it can be tested, even without any e-stim devices connected!