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