diff --git a/BetterGenshinImpact/BetterGenshinImpact.csproj b/BetterGenshinImpact/BetterGenshinImpact.csproj
index df18c8ac..e0098b93 100644
--- a/BetterGenshinImpact/BetterGenshinImpact.csproj
+++ b/BetterGenshinImpact/BetterGenshinImpact.csproj
@@ -49,6 +49,7 @@
+
diff --git a/BetterGenshinImpact/Core/Config/CommonConfig.cs b/BetterGenshinImpact/Core/Config/CommonConfig.cs
index 5c92e1f6..8bbd8997 100644
--- a/BetterGenshinImpact/Core/Config/CommonConfig.cs
+++ b/BetterGenshinImpact/Core/Config/CommonConfig.cs
@@ -42,4 +42,8 @@ public partial class CommonConfig : ObservableObject
// 关闭时还原分辨率
[ObservableProperty]
private bool _restoreResolutionOnExit;
+
+ // 录制工具 ffmpeg/obs
+ [ObservableProperty]
+ private string _recorder = "ffmpeg";
}
diff --git a/BetterGenshinImpact/Core/Recorder/GlobalKeyMouseRecord.cs b/BetterGenshinImpact/Core/Recorder/GlobalKeyMouseRecord.cs
index 6b8c667c..19ef6619 100644
--- a/BetterGenshinImpact/Core/Recorder/GlobalKeyMouseRecord.cs
+++ b/BetterGenshinImpact/Core/Recorder/GlobalKeyMouseRecord.cs
@@ -29,7 +29,7 @@ public class GlobalKeyMouseRecord : Singleton
// private SharpAviRecorder _sharpAviRecorder;
- private FfmpegRecorder? _ffmpegRecorder;
+ private IVideoRecorder? _videoRecorder;
private readonly Dictionary _keyDownState = [];
@@ -64,6 +64,11 @@ public class GlobalKeyMouseRecord : Singleton
Toast.Warning("已经在录制状态,请不要重复启动录制功能");
return;
}
+
+ if (TaskContext.Instance().Config.CommonConfig.Recorder == "obs")
+ {
+ TaskControl.Logger.LogInformation("当前选择使用OBS录制,OBS启动较慢,请耐心等待...");
+ }
_keyMouseMacroRecordHotkey = TaskContext.Instance().Config.HotKeyConfig.KeyMouseMacroRecordHotkey;
_paimonSwitchEnabled = TaskContext.Instance().Config.RecordConfig.PaimonSwitchEnabled;
@@ -80,7 +85,7 @@ public class GlobalKeyMouseRecord : Singleton
// _sharpAviRecorder = new SharpAviRecorder( Path.Combine(videoPath, $"{DateTime.Now:yyyyMMddHH_mmssffff.avi}"),
// CodecIds.MotionJpeg, 90, 0, SupportedWaveFormat.WAVE_FORMAT_44M16, false, 0);
- _ffmpegRecorder = new FfmpegRecorder(fileName);
+ _videoRecorder = VideoRecorderFactory.Create(TaskContext.Instance().Config.CommonConfig.Recorder, fileName);
_directInputMonitor = new DirectInputMonitor();
// TaskTriggerDispatcher.Instance().StopTimer();
@@ -98,7 +103,8 @@ public class GlobalKeyMouseRecord : Singleton
}
- _ffmpegRecorder.Start();
+ var videoEnabled = _videoRecorder.Start();
+
_directInputMonitor.Start();
_recorder = new KeyMouseRecorderJsonLine(fileName);
@@ -120,7 +126,7 @@ public class GlobalKeyMouseRecord : Singleton
_directInputMonitor?.Dispose();
_directInputMonitor = null;
- _ffmpegRecorder?.Stop();
+ _videoRecorder?.Stop();
if (_timer.Enabled)
{
diff --git a/BetterGenshinImpact/Core/Video/FfmpegRecorder.cs b/BetterGenshinImpact/Core/Video/FfmpegRecorder.cs
index 13f64362..7a2680ef 100644
--- a/BetterGenshinImpact/Core/Video/FfmpegRecorder.cs
+++ b/BetterGenshinImpact/Core/Video/FfmpegRecorder.cs
@@ -13,7 +13,7 @@ using Serilog.Core;
namespace BetterGenshinImpact.Core.Video;
-public class FfmpegRecorder
+public class FfmpegRecorder : IVideoRecorder
{
// ffmpeg进程
private readonly Process _process;
@@ -132,4 +132,9 @@ public class FfmpegRecorder
TaskControl.Logger.LogError("ffmpeg录制: 停止时异常:{Text}", e.Message);
}
}
+
+ public void Dispose()
+ {
+
+ }
}
\ No newline at end of file
diff --git a/BetterGenshinImpact/Core/Video/IVideoRecorder.cs b/BetterGenshinImpact/Core/Video/IVideoRecorder.cs
new file mode 100644
index 00000000..1ea936ad
--- /dev/null
+++ b/BetterGenshinImpact/Core/Video/IVideoRecorder.cs
@@ -0,0 +1,10 @@
+using System;
+
+namespace BetterGenshinImpact.Core.Video;
+
+public interface IVideoRecorder : IDisposable
+{
+ public bool Start();
+
+ public void Stop();
+}
\ No newline at end of file
diff --git a/BetterGenshinImpact/Core/Video/ObsRecorder.cs b/BetterGenshinImpact/Core/Video/ObsRecorder.cs
new file mode 100644
index 00000000..fa59799e
--- /dev/null
+++ b/BetterGenshinImpact/Core/Video/ObsRecorder.cs
@@ -0,0 +1,159 @@
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using BetterGenshinImpact.Core.Config;
+using BetterGenshinImpact.GameTask.Common;
+using Microsoft.Extensions.Logging;
+using OBSWebsocketDotNet;
+using OBSWebsocketDotNet.Communication;
+
+namespace BetterGenshinImpact.Core.Video;
+
+public class ObsRecorder : IVideoRecorder
+{
+ private static readonly string ObsPath = Global.Absolute(@"video\bin\OBS-Studio-31.0.0-Windows\bin\64bit\obs64.exe");
+ private Process? _obs64Process;
+
+ private OBSWebsocket obs;
+ private bool isConnected = false;
+
+ public ObsRecorder()
+ {
+ // 判断 OBS 是否已经启动
+ if (Process.GetProcessesByName("obs64").Length == 0)
+ {
+ // 启动 OBS 并等待启动完成
+ ProcessStartInfo startInfo = new ProcessStartInfo
+ {
+ FileName = ObsPath,
+ Arguments = "--disable-shutdown-check --disable-missing-files-check --disable-updater",
+ WorkingDirectory = Path.GetDirectoryName(ObsPath)
+ };
+
+ _obs64Process = Process.Start(startInfo);
+ if (_obs64Process != null)
+ {
+ _obs64Process.WaitForInputIdle(); // Wait for the process to be ready for input
+ Debug.WriteLine("OBS has started and is ready for input.");
+ TaskControl.Logger.LogInformation("OBS: 启动完成");
+ }
+ else
+ {
+ TaskControl.Logger.LogError("OBS: 启动失败");
+ throw new Exception("OBS启动失败");
+ }
+ }
+
+ obs = new OBSWebsocket();
+
+ // 注册连接事件处理
+ obs.Connected += OnConnected;
+ obs.Disconnected += OnDisconnected;
+
+ Connect();
+ }
+
+ // 连接到 OBS
+ public void Connect(string url = "ws://localhost:44557", string password = "huiyadanli@789")
+ {
+ try
+ {
+ obs.ConnectAsync(url, password);
+ }
+ catch (AuthFailureException)
+ {
+ TaskControl.Logger.LogError("OBS: 验证失败 - 密码错误");
+ throw;
+ }
+ catch (ErrorResponseException ex)
+ {
+ TaskControl.Logger.LogError($"连接失败: {ex.Message}");
+ throw;
+ }
+ catch (Exception ex)
+ {
+ TaskControl.Logger.LogError($"连接失败: {ex.Message}");
+ throw;
+ }
+ }
+
+ // 开始录制
+ public bool Start()
+ {
+ for (int i = 0; i < 10; i++)
+ {
+ if (obs.IsConnected)
+ {
+ try
+ {
+ obs.StartRecord();
+ TaskControl.Logger.LogInformation("OBS: 开始录制");
+ return true;
+ }
+ catch (ErrorResponseException ex)
+ {
+ if (ex.ErrorCode == 207)
+ {
+ TaskControl.Logger.LogInformation("207错误,等待连接 OBS 就绪...重试次数: {Count}", i + 1);
+ Thread.Sleep(1000);
+ }
+ else
+ {
+ TaskControl.Logger.LogError($"OBS: 开始录制失败: {ex.Message}");
+ throw;
+ }
+ }
+ }
+ else
+ {
+ Thread.Sleep(1000);
+ TaskControl.Logger.LogInformation("等待连接 OBS 连接...重试次数: {Count}", i + 1);
+ }
+ }
+
+
+ TaskControl.Logger.LogError("OBS: 启动录制失败,未连接到 OBS");
+ return false;
+ }
+
+ // 停止录制
+ public void Stop()
+ {
+ if (obs.IsConnected)
+ {
+ var path = obs.StopRecord();
+ TaskControl.Logger.LogInformation("OBS: 停止录制录制");
+ TaskControl.Logger.LogInformation("OBS: 文件存储在 {Path}", path);
+ }
+ }
+
+ private void OnConnected(object? sender, EventArgs e)
+ {
+ isConnected = true;
+ TaskControl.Logger.LogInformation("OBS: 成功连接");
+ }
+
+ private void OnDisconnected(object? sender, ObsDisconnectionInfo e)
+ {
+ if (isConnected)
+ {
+ TaskControl.Logger.LogWarning("OBS: 断开连接, 原因: {Reason}", e.DisconnectReason);
+ }
+ else
+ {
+ TaskControl.Logger.LogError("OBS: 断开连接, 原因: {Reason}", e.DisconnectReason);
+ }
+
+ isConnected = false;
+ }
+
+ public void Dispose()
+ {
+ if (obs.IsConnected)
+ {
+ obs.Disconnect();
+ }
+ }
+}
\ No newline at end of file
diff --git a/BetterGenshinImpact/Core/Video/VideoRecorderFactory.cs b/BetterGenshinImpact/Core/Video/VideoRecorderFactory.cs
new file mode 100644
index 00000000..37d23d1b
--- /dev/null
+++ b/BetterGenshinImpact/Core/Video/VideoRecorderFactory.cs
@@ -0,0 +1,25 @@
+using System;
+using BetterGenshinImpact.GameTask.Common;
+using Microsoft.Extensions.Logging;
+
+namespace BetterGenshinImpact.Core.Video;
+
+public class VideoRecorderFactory
+{
+
+ private static ObsRecorder? _obsRecorder;
+
+ public static IVideoRecorder Create(string recorderType, string fileName)
+ {
+ switch (recorderType)
+ {
+ case "ffmpeg":
+ return new FfmpegRecorder(fileName);
+ case "obs":
+ _obsRecorder ??= new ObsRecorder();
+ return _obsRecorder;
+ default:
+ throw new ArgumentException("不支持的录制工具");
+ }
+ }
+}
\ No newline at end of file
diff --git a/BetterGenshinImpact/Helpers/Device/TouchpadManager.cs b/BetterGenshinImpact/Helpers/Device/TouchpadManager.cs
index be76040a..0b460b40 100644
--- a/BetterGenshinImpact/Helpers/Device/TouchpadManager.cs
+++ b/BetterGenshinImpact/Helpers/Device/TouchpadManager.cs
@@ -103,14 +103,20 @@ public static class TouchpadManager
public static bool HasTouchInput()
{
- foreach (TabletDevice tabletDevice in Tablet.TabletDevices)
+ bool hasTouchInput = false;
+ UIDispatcherHelper.Invoke(()=>
{
- //Only detect if it is a touch Screen not how many touches (i.e. Single touch or Multi-touch)
- if(tabletDevice.Type == TabletDeviceType.Touch)
- return true;
- }
+ foreach (TabletDevice tabletDevice in Tablet.TabletDevices)
+ {
+ //Only detect if it is a touch Screen not how many touches (i.e. Single touch or Multi-touch)
+ if(tabletDevice.Type == TabletDeviceType.Touch)
+ {
+ hasTouchInput= true;
+ }
+ }
+ });
- return false;
+ return hasTouchInput;
}
diff --git a/BetterGenshinImpact/Service/Singletons/StartEndSingleton.cs b/BetterGenshinImpact/Service/Singletons/StartEndSingleton.cs
index fd5bb22d..4eea63f0 100644
--- a/BetterGenshinImpact/Service/Singletons/StartEndSingleton.cs
+++ b/BetterGenshinImpact/Service/Singletons/StartEndSingleton.cs
@@ -1,5 +1,7 @@
using System;
+using System.Diagnostics;
using System.IO;
+using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using BetterGenshinImpact.Core.Config;
@@ -68,6 +70,16 @@ public class StartEndSingleton: Singleton
SysDpi.Instance.ResetDpi();
}
+ try
+ {
+ Process.GetProcessesByName("obs64").ToList().ForEach(p => p.Kill());
+ Process.GetProcessesByName("ffmpeg").ToList().ForEach(p => p.Kill());
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine(e);
+ throw;
+ }
}
diff --git a/BetterGenshinImpact/View/Pages/CommonSettingsPage.xaml b/BetterGenshinImpact/View/Pages/CommonSettingsPage.xaml
index 1a5f27c5..14c56930 100644
--- a/BetterGenshinImpact/View/Pages/CommonSettingsPage.xaml
+++ b/BetterGenshinImpact/View/Pages/CommonSettingsPage.xaml
@@ -501,6 +501,33 @@
Margin="0,0,36,0"
IsChecked="{Binding Config.CommonConfig.RestoreResolutionOnExit, Mode=TwoWay}" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/BetterGenshinImpact/ViewModel/Pages/CommonSettingsPageViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/CommonSettingsPageViewModel.cs
index 6e1b1f35..3fd16323 100644
--- a/BetterGenshinImpact/ViewModel/Pages/CommonSettingsPageViewModel.cs
+++ b/BetterGenshinImpact/ViewModel/Pages/CommonSettingsPageViewModel.cs
@@ -28,7 +28,10 @@ public partial class CommonSettingsPageViewModel : ObservableObject, INavigation
[ObservableProperty] private bool _isLoading;
[ObservableProperty] private string _webhookStatus = string.Empty;
-
+
+
+ [ObservableProperty]
+ private string[] _recorderTypes = ["ffmpeg", "obs"];
public CommonSettingsPageViewModel(IConfigService configService, INavigationService navigationService, NotificationService notificationService)
{
diff --git a/BetterGenshinImpact/ViewModel/Pages/KeyMouseRecordPageViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/KeyMouseRecordPageViewModel.cs
index 3096542b..71a074d6 100644
--- a/BetterGenshinImpact/ViewModel/Pages/KeyMouseRecordPageViewModel.cs
+++ b/BetterGenshinImpact/ViewModel/Pages/KeyMouseRecordPageViewModel.cs
@@ -128,10 +128,10 @@ public partial class KeyMouseRecordPageViewModel : ObservableObject, INavigation
return;
}
}
-
+
fileName = $"{DateTime.Now:yyyy_MM_dd_HH_mm_ss}";
-
+
await Task.Run(() =>
{
try
@@ -139,7 +139,11 @@ public partial class KeyMouseRecordPageViewModel : ObservableObject, INavigation
var pcFolder = Global.Absolute(@$"User/KeyMouseScript/{fileName}");
Directory.CreateDirectory(pcFolder);
// 移动PC信息
- File.Copy(Global.Absolute(@$"User/pc.json"), Path.Combine(pcFolder, "pc.json"), true);
+ var src= Global.Absolute(@$"User/pc.json");
+ if (File.Exists(src))
+ {
+ File.Copy(Global.Absolute(@$"User/pc.json"), Path.Combine(pcFolder, "pc.json"), true);
+ }
}
catch (Exception e)
{
@@ -148,15 +152,22 @@ public partial class KeyMouseRecordPageViewModel : ObservableObject, INavigation
});
-
if (!IsRecording)
{
IsRecording = true;
SystemSettingsManager.GetSystemSettings();
SystemSettingsManager.SetSystemSettings();
-
- await GlobalKeyMouseRecord.Instance.StartRecord(fileName);
+ try
+ {
+ await GlobalKeyMouseRecord.Instance.StartRecord(fileName);
+ }
+ catch (Exception e)
+ {
+ _logger.LogDebug(e, "启动录制时发生异常");
+ _logger.LogError(e.Message);
+ IsRecording = false;
+ }
}
}
@@ -177,9 +188,30 @@ public partial class KeyMouseRecordPageViewModel : ObservableObject, INavigation
catch (Exception e)
{
_logger.LogDebug(e, "停止录制时发生异常");
- _logger.LogWarning(e.Message);
+ _logger.LogError(e.Message);
}
+
SystemSettingsManager.RestoreSystemSettings();
+
+
+ Task.Run(() =>
+ {
+ try
+ {
+ var pcFolder = Global.Absolute(@$"User/KeyMouseScript/{fileName}");
+ Directory.CreateDirectory(pcFolder);
+ // 移动PC信息
+ var src= Global.Absolute(@$"User/pc.json");
+ if (File.Exists(src))
+ {
+ File.Copy(Global.Absolute(@$"User/pc.json"), Path.Combine(pcFolder, "pc.json"), true);
+ }
+ }
+ catch (Exception e)
+ {
+ TaskControl.Logger.LogDebug("移动PC信息失败:" + e.Source + "\r\n--" + Environment.NewLine + e.StackTrace + "\r\n---" + Environment.NewLine + e.Message);
+ }
+ });
}
}