diff --git a/Plugins Store/Totoro.Plugins.MediaDetection.Generic.dll b/Plugins Store/Totoro.Plugins.MediaDetection.Generic.dll index a24ef033..49468483 100644 Binary files a/Plugins Store/Totoro.Plugins.MediaDetection.Generic.dll and b/Plugins Store/Totoro.Plugins.MediaDetection.Generic.dll differ diff --git a/Plugins Store/Totoro.Plugins.MediaDetection.MpcHc.dll b/Plugins Store/Totoro.Plugins.MediaDetection.MpcHc.dll new file mode 100644 index 00000000..63d2138a Binary files /dev/null and b/Plugins Store/Totoro.Plugins.MediaDetection.MpcHc.dll differ diff --git a/Plugins Store/Totoro.Plugins.MediaDetection.Vlc.dll b/Plugins Store/Totoro.Plugins.MediaDetection.Vlc.dll index d19d6b87..b2f2f78a 100644 Binary files a/Plugins Store/Totoro.Plugins.MediaDetection.Vlc.dll and b/Plugins Store/Totoro.Plugins.MediaDetection.Vlc.dll differ diff --git a/Plugins Store/Totoro.Plugins.MediaDetection.Win11MediaPlayer.dll b/Plugins Store/Totoro.Plugins.MediaDetection.Win11MediaPlayer.dll index 28de3f52..71c88dc6 100644 Binary files a/Plugins Store/Totoro.Plugins.MediaDetection.Win11MediaPlayer.dll and b/Plugins Store/Totoro.Plugins.MediaDetection.Win11MediaPlayer.dll differ diff --git a/Plugins/Media Detection/Totoro.Plugins.MediaDetection.MpcHc/Config.cs b/Plugins/Media Detection/Totoro.Plugins.MediaDetection.MpcHc/Config.cs new file mode 100644 index 00000000..1e39e2e7 --- /dev/null +++ b/Plugins/Media Detection/Totoro.Plugins.MediaDetection.MpcHc/Config.cs @@ -0,0 +1,19 @@ +using System.ComponentModel; +using Totoro.Plugins.Options; + +namespace Totoro.Plugins.MediaDetection.MpcHc; + +public class Config : ConfigObject +{ + [DisplayName("Path")] + [Description("Path to executable")] + [Glyph(Glyphs.File)] + public string FileName { get; set; } = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), @"MPC-HC\mpc-hc64.exe"); + + [Glyph(Glyphs.Website)] + public string Host { get; set; } = "127.0.0.1"; + + [Glyph(Glyphs.Port)] + [Description("View > Options > Web Interface > Listen on port")] + public int Port { get; set; } = 13579; +} \ No newline at end of file diff --git a/Plugins/Media Detection/Totoro.Plugins.MediaDetection.MpcHc/MediaPlayer.cs b/Plugins/Media Detection/Totoro.Plugins.MediaDetection.MpcHc/MediaPlayer.cs new file mode 100644 index 00000000..470db327 --- /dev/null +++ b/Plugins/Media Detection/Totoro.Plugins.MediaDetection.MpcHc/MediaPlayer.cs @@ -0,0 +1,54 @@ +using System.Diagnostics; +using System.Reactive.Linq; +using FlaUI.Core; +using FlaUI.Core.AutomationElements; +using ReactiveUI; +using Totoro.Plugins.MediaDetection.Contracts; +using Totoro.Plugins.Options; + +namespace Totoro.Plugins.MediaDetection.MpcHc +{ + internal sealed partial class MediaPlayer : ReactiveObject, INativeMediaPlayer, IHavePosition, ICanLaunch + { + private string? _customTitle; + private MpvHcInterface _interface = null!; + + public IObservable PositionChanged { get; private set; } = null!; + public IObservable DurationChanged { get; private set; } = null!; + public IObservable TitleChanged { get; private set; } = null!; + public Process? Process { get; private set; } + + + public Task Launch(string title, string url) + { + _customTitle = title; + var app = Application.Launch(ConfigManager.Current.FileName, $"{url} /fullscreen"); + InitializeInternal(Process.GetProcessById(app.ProcessId), true); + return Task.CompletedTask; + } + + public Task Initialize(Window window) + { + Process = Process.GetProcessById(window.Properties.ProcessId); + InitializeInternal(Process); + return Task.CompletedTask; + } + + private void InitializeInternal(Process process, bool hasCustomTitle = false) + { + _interface = new MpvHcInterface(process); + + TitleChanged = hasCustomTitle + ? Observable.Return(_customTitle!) + : _interface.TitleChanged; + + DurationChanged = _interface.DurationChanged; + PositionChanged = _interface.PositionChanged; + } + + public void Dispose() + { + _interface.Dispose(); + } + } +} diff --git a/Plugins/Media Detection/Totoro.Plugins.MediaDetection.MpcHc/MpvHcInterface.cs b/Plugins/Media Detection/Totoro.Plugins.MediaDetection.MpcHc/MpvHcInterface.cs new file mode 100644 index 00000000..a5c9c891 --- /dev/null +++ b/Plugins/Media Detection/Totoro.Plugins.MediaDetection.MpcHc/MpvHcInterface.cs @@ -0,0 +1,101 @@ +using System.Diagnostics; +using System.Reactive.Disposables; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using System.Text.RegularExpressions; +using Flurl; +using Flurl.Http; +using ReactiveUI; +using Totoro.Plugins.Options; + +namespace Totoro.Plugins.MediaDetection.MpcHc; + +internal sealed partial class MpvHcInterface : IDisposable +{ + private readonly string _api; + private readonly CompositeDisposable _disposables = new(); + private readonly ReplaySubject _titleChanged = new(); + private readonly ReplaySubject _durationChanged = new(); + private readonly ReplaySubject _timeChanged = new(); + + [GeneratedRegex(@"

(file|positionstring|durationstring))"">(?[^<>]*)")] + private partial Regex VariablesRegex(); + + public IObservable TitleChanged { get; } + public IObservable DurationChanged { get; } + public IObservable PositionChanged { get; } + + public MpvHcInterface(Process process) + { + var host = ConfigManager.Current.Host; + var port = ConfigManager.Current.Port; + _api = $"http://{host}:{port}"; + + Observable.Timer(TimeSpan.Zero, TimeSpan.FromSeconds(1)) + .Where(_ => !process.HasExited) + .SelectMany(_ => GetVariables()) + .WhereNotNull() + .ObserveOn(RxApp.MainThreadScheduler) + .Subscribe(variables => + { + if (!string.IsNullOrEmpty(variables.Title)) + { + _titleChanged.OnNext(variables.Title); + } + _durationChanged.OnNext(variables.Duration); + _timeChanged.OnNext(variables.Time); + }) + .DisposeWith(_disposables); + + TitleChanged = _titleChanged.DistinctUntilChanged(); + DurationChanged = _durationChanged.DistinctUntilChanged(); + PositionChanged = _timeChanged.DistinctUntilChanged(); + } + + private async Task GetVariables() + { + var result = await _api.AppendPathSegment("variables.html").GetAsync(); + + if(result.StatusCode >= 300) + { + return null; + } + + var html = await result.GetStringAsync(); + var variables = new Variables(); + + foreach (var match in VariablesRegex().Matches(html).OfType().Where(x => x.Success)) + { + var name = match.Groups["Variable"].Value; + var value = match.Groups["Value"].Value; + + if (name == "file") + { + variables.Title = Path.GetFileNameWithoutExtension(value); + } + else if(name == "positionstring") + { + variables.Time = TimeSpan.Parse(value); + } + else if(name == "durationstring") + { + variables.Duration = TimeSpan.Parse(value); + } + } + + return variables; + } + + public void Dispose() + { + _disposables.Dispose(); + } + +} + +internal class Variables +{ + public string Title { get; set; } = string.Empty; + public TimeSpan Duration { get; set; } + public TimeSpan Time { get; set; } +} diff --git a/Plugins/Media Detection/Totoro.Plugins.MediaDetection.MpcHc/Plugin.cs b/Plugins/Media Detection/Totoro.Plugins.MediaDetection.MpcHc/Plugin.cs new file mode 100644 index 00000000..a20770df --- /dev/null +++ b/Plugins/Media Detection/Totoro.Plugins.MediaDetection.MpcHc/Plugin.cs @@ -0,0 +1,22 @@ +using System.Reflection; +using Totoro.Plugins.Contracts; +using Totoro.Plugins.MediaDetection.Contracts; + +namespace Totoro.Plugins.MediaDetection.MpcHc; + +public class Plugin : Plugin +{ + public override INativeMediaPlayer Create() => new MediaPlayer(); + + public override PluginInfo GetInfo() + { + return new PluginInfo + { + Name = "mpc-hc64", + DisplayName = "MPC-HC", + Description = "", + Version = Assembly.GetExecutingAssembly().GetName().Version!, + Icon = typeof(Plugin).Assembly.GetManifestResourceStream("Totoro.Plugins.MediaDetection.MpcHc.mpc-hc-logo.png"), + }; + } +} diff --git a/Plugins/Media Detection/Totoro.Plugins.MediaDetection.MpcHc/Totoro.Plugins.MediaDetection.MpcHc.csproj b/Plugins/Media Detection/Totoro.Plugins.MediaDetection.MpcHc/Totoro.Plugins.MediaDetection.MpcHc.csproj new file mode 100644 index 00000000..30e096bd --- /dev/null +++ b/Plugins/Media Detection/Totoro.Plugins.MediaDetection.MpcHc/Totoro.Plugins.MediaDetection.MpcHc.csproj @@ -0,0 +1,22 @@ + + + + net8.0-windows + enable + enable + x64 + 1.0 + ..\..\..\Plugins Store\ + false + false + + + + + + + + + + + diff --git a/Plugins/Media Detection/Totoro.Plugins.MediaDetection.MpcHc/mpc-hc-logo.png b/Plugins/Media Detection/Totoro.Plugins.MediaDetection.MpcHc/mpc-hc-logo.png new file mode 100644 index 00000000..d0a07de5 Binary files /dev/null and b/Plugins/Media Detection/Totoro.Plugins.MediaDetection.MpcHc/mpc-hc-logo.png differ diff --git a/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Mpv/Config.cs b/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Mpv/Config.cs index 10fd0ae5..2df4ba2f 100644 --- a/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Mpv/Config.cs +++ b/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Mpv/Config.cs @@ -1,13 +1,12 @@ -using Totoro.Plugins.Options; +using System.ComponentModel; +using Totoro.Plugins.Options; namespace Totoro.Plugins.MediaDetection.Generic; -public class MpcConfig : ConfigObject -{ - public string FileName { get; set; } = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), @"MPC-HC\mpc-hc64.exe"); -} - public class MpvConfig : ConfigObject { + [DisplayName("Path")] + [Description("Path to executable")] + [Glyph(Glyphs.File)] public string FileName { get; set; } = @""; } diff --git a/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Mpv/MediaPlayer.cs b/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Mpv/MediaPlayer.cs index 9de15209..6bae458d 100644 --- a/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Mpv/MediaPlayer.cs +++ b/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Mpv/MediaPlayer.cs @@ -1,76 +1,40 @@ -using System.Diagnostics; -using FlaUI.Core; -using FlaUI.Core.AutomationElements; +using FlaUI.Core; using Totoro.Plugins.MediaDetection.Contracts; using Totoro.Plugins.Options; namespace Totoro.Plugins.MediaDetection.Generic; -public class Mpv : GenericMediaPlayer, ICanLaunch +public sealed class Mpv : GenericMediaPlayer, ICanLaunch { private bool _hasCustomTitle; private string? _customTitle; + private Application? _application; - protected override string ParseFromWindowTitle(string windowTitle) + protected override Task ParseFromWindowTitle(string windowTitle) { - return windowTitle.Replace("- mpv", string.Empty); + return Task.FromResult(windowTitle.Replace("- mpv", string.Empty)); } - public override string GetTitle() + public override Task GetTitle() { if (_hasCustomTitle) { - return _customTitle!; + return Task.FromResult(_customTitle!); } return base.GetTitle(); } - public void Launch(string title, string url) + public Task Launch(string title, string url) { _hasCustomTitle = true; _customTitle = title; - Application.Launch(ConfigManager.Current.FileName, $"{url} --title=\"{title}\" --fs"); - } -} - -public sealed class MpcHc : INativeMediaPlayer, ICanLaunch -{ - private Window? _window; - private bool _hasCustomTitle; - private string? _customTitle; - - public Process? Process { get; private set; } - - public void Dispose() { } - - public string GetTitle() - { - if (_hasCustomTitle) - { - return _customTitle!; - } - - var title = _window!.Title; - while (title == "Media Player Classic Home Cinema") - { - title = _window!.Title; - Thread.Sleep(100); - } - - return title; + _application = Application.Launch(ConfigManager.Current.FileName, $"{url} --title=\"{title}\" --fs"); + return Task.CompletedTask; } - public void Initialize(Window window) + public override void Dispose() { - _window = window; - Process = Process.GetProcessById(window.Properties.ProcessId); - } - - public void Launch(string title, string url) - { - _hasCustomTitle = true; - _customTitle = title; - Application.Launch(ConfigManager.Current.FileName, $"{url} /fullscreen"); + _application?.Dispose(); } } diff --git a/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Mpv/Plugin.cs b/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Mpv/Plugin.cs index 65186fe8..0f717549 100644 --- a/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Mpv/Plugin.cs +++ b/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Mpv/Plugin.cs @@ -21,21 +21,8 @@ public override PluginInfo GetInfo() Name = "mpv", DisplayName = "MPV", Description = "", - Version = Assembly.GetExecutingAssembly().GetName().Version! - }; - } -} - -public class MpcHcPlugin : GenericPlugin -{ - public override PluginInfo GetInfo() - { - return new PluginInfo - { - Name = "mpc-hc64", - DisplayName = "MPC-HC", - Description = "", - Version = Assembly.GetExecutingAssembly().GetName().Version! + Version = Assembly.GetExecutingAssembly().GetName().Version!, + Icon = typeof(MpvPlugin).Assembly.GetManifestResourceStream("Totoro.Plugins.MediaDetection.Generic.mpv-icon.png"), }; } } \ No newline at end of file diff --git a/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Mpv/Totoro.Plugins.MediaDetection.Generic.csproj b/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Mpv/Totoro.Plugins.MediaDetection.Generic.csproj index 90735b02..8138c628 100644 --- a/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Mpv/Totoro.Plugins.MediaDetection.Generic.csproj +++ b/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Mpv/Totoro.Plugins.MediaDetection.Generic.csproj @@ -5,12 +5,16 @@ enable enable x64 - 1.4 + 1.5 ..\..\..\Plugins Store\ false false + + + + diff --git a/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Mpv/mpv-icon.png b/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Mpv/mpv-icon.png new file mode 100644 index 00000000..b0574307 Binary files /dev/null and b/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Mpv/mpv-icon.png differ diff --git a/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Vlc/Config.cs b/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Vlc/Config.cs new file mode 100644 index 00000000..b6682e12 --- /dev/null +++ b/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Vlc/Config.cs @@ -0,0 +1,23 @@ +using System.ComponentModel; +using Totoro.Plugins.Options; + +namespace Totoro.Plugins.MediaDetection.Vlc; + +public class Config : ConfigObject +{ + [DisplayName("Path")] + [Description("Path to executable")] + [Glyph(Glyphs.File)] + public string FileName { get; set; } = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), @"VideoLAN\VLC\vlc.exe"); + + [Glyph(Glyphs.Website)] + [Description("Enable web interface Preferences > Interface > Main Interfaces > Web Interface")] + public string Host { get; set; } = "127.0.0.1"; + + [Glyph(Glyphs.Port)] + public int Port { get; set; } = 8080; + + [Glyph(Glyphs.Password)] + [Description("Password set in Preferences > Interface > Main Interfaces > Lua > Lua HTTP password")] + public string Password { get; set; } = ""; +} \ No newline at end of file diff --git a/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Vlc/HttpInterface/Models.cs b/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Vlc/HttpInterface/Models.cs new file mode 100644 index 00000000..072d5a21 --- /dev/null +++ b/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Vlc/HttpInterface/Models.cs @@ -0,0 +1,33 @@ +using System.Text.Json.Serialization; + +namespace Totoro.Plugins.MediaDetection.Vlc.HttpInterface; + +internal class VlcStatus +{ + [JsonPropertyName("time")] + public int Time { get; set; } + + [JsonPropertyName("length")] + public int Length { get; set; } + + [JsonPropertyName("information")] + public Information Information { get; set; } = new Information(); +} + +internal class Information +{ + [JsonPropertyName("category")] + public Category Category { get; set; } = new(); +} + +internal class Category +{ + [JsonPropertyName("meta")] + public Meta Meta { get; set; } = new(); +} + +internal class Meta +{ + [JsonPropertyName("filename")] + public string FileName { get; set; } = string.Empty; +} diff --git a/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Vlc/HttpInterface/VlcInterface.cs b/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Vlc/HttpInterface/VlcInterface.cs new file mode 100644 index 00000000..f6e3565d --- /dev/null +++ b/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Vlc/HttpInterface/VlcInterface.cs @@ -0,0 +1,91 @@ +using System.Diagnostics; +using System.Reactive.Disposables; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using Flurl; +using Flurl.Http; +using ReactiveUI; +using Totoro.Plugins.Options; + +namespace Totoro.Plugins.MediaDetection.Vlc.HttpInterface +{ + internal sealed class VlcInterface : IDisposable + { + private readonly string _api; + private readonly CompositeDisposable _disposables = new(); + private readonly ReplaySubject _titleChanged = new(); + private readonly ReplaySubject _durationChanged = new(); + private readonly ReplaySubject _timeChanged = new(); + + public IObservable TitleChanged { get; } + public IObservable DurationChanged { get; } + public IObservable PositionChanged { get; } + + public VlcInterface(Process process) + { + var host = ConfigManager.Current.Host; + var port = ConfigManager.Current.Port; + _api = $"http://{host}:{port}"; + + Observable.Timer(TimeSpan.Zero, TimeSpan.FromSeconds(1)) + .Where(_ => !process.HasExited) + .SelectMany(_ => GetStatus()) + .WhereNotNull() + .ObserveOn(RxApp.MainThreadScheduler) + .Subscribe(status => + { + if(!string.IsNullOrEmpty(status!.Information?.Category?.Meta?.FileName)) + { + _titleChanged.OnNext(Path.GetFileNameWithoutExtension(status!.Information.Category.Meta.FileName)); + } + _durationChanged.OnNext(TimeSpan.FromSeconds(status!.Length)); + _timeChanged.OnNext(TimeSpan.FromSeconds(status!.Time)); + }) + .DisposeWith(_disposables); + + TitleChanged = _titleChanged.DistinctUntilChanged(); + DurationChanged = _durationChanged.DistinctUntilChanged(); + PositionChanged = _timeChanged.DistinctUntilChanged(); + } + + + public async Task GetStatus() + { + var result = await _api + .AppendPathSegment("/requests/status.json") + .WithBasicAuth("", "password").GetAsync(); + + if(result.StatusCode >= 300) + { + return null; + } + + return await result.GetJsonAsync(); + } + + public async Task SeekTo(TimeSpan timeSpan) + { + _ = await _api + .AppendPathSegment("/requets/status.json") + .SetQueryParam("command", "seek") + .SetQueryParam("val", timeSpan.TotalSeconds) + .WithBasicAuth("", "password") + .GetAsync(); + } + + public async Task SetVolume(int percent) + { + _ = await _api + .AppendPathSegment("/requets/status.json") + .SetQueryParam("command", "volume") + .SetQueryParam("val", $"{percent}%") + .WithBasicAuth("", "password") + .GetAsync(); + } + + public void Dispose() + { + _disposables.Dispose(); + } + } +} diff --git a/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Vlc/MediaPlayer.cs b/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Vlc/MediaPlayer.cs index 86f3580a..fdadc26d 100644 --- a/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Vlc/MediaPlayer.cs +++ b/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Vlc/MediaPlayer.cs @@ -1,133 +1,55 @@ using System.Diagnostics; using System.Reactive.Linq; -using System.Reactive.Subjects; -using System.Text.RegularExpressions; using FlaUI.Core; using FlaUI.Core.AutomationElements; -using FlaUI.Core.Definitions; -using FlaUI.UIA3; using ReactiveUI; -using ReactiveUI.Fody.Helpers; using Totoro.Plugins.MediaDetection.Contracts; +using Totoro.Plugins.MediaDetection.Vlc.HttpInterface; using Totoro.Plugins.Options; namespace Totoro.Plugins.MediaDetection.Vlc { internal sealed partial class MediaPlayer : ReactiveObject, INativeMediaPlayer, IHavePosition, ICanLaunch { - private Application? _application; - private Window? _mainWindow; - private Slider? _slider; - private readonly Subject _positionChanged = new(); - private bool _hasCustomTitle; private string? _customTitle; + private VlcInterface _interface = null!; - [Reactive] TimeSpan Duration { get; set; } - public IObservable PositionChanged => _positionChanged; - public IObservable DurationChanged { get; } + public IObservable PositionChanged { get; private set; } = null!; + public IObservable DurationChanged { get; private set; } = null!; + public IObservable TitleChanged { get; private set; } = null!; public Process? Process { get; private set; } - public MediaPlayer() - { - DurationChanged = this.WhenAnyValue(x => x.Duration); - } - - public string GetTitle() - { - if (_hasCustomTitle) - { - return _customTitle!; - } - - if (_mainWindow is null) - { - return ""; - } - var title = _mainWindow.Title; - - return title.Replace("- Vlc media player", string.Empty, StringComparison.InvariantCultureIgnoreCase).Trim(); - } - - public void Launch(string title, string url) + public Task Launch(string title, string url) { - _hasCustomTitle = true; _customTitle = title; - _application = Application.Launch(ConfigManager.Current.FileName, $"{url} --meta-title=\"{title}\" -f"); - InitializeInternal(); + var app = Application.Launch(ConfigManager.Current.FileName, $"{url} --meta-title=\"{title}\" -f"); + InitializeInternal(Process.GetProcessById(app.ProcessId), true); + return Task.CompletedTask; } - public void Initialize(Window window) + public Task Initialize(Window window) { - _mainWindow = window; Process = Process.GetProcessById(window.Properties.ProcessId); - InitializeInternal(); - } - - public void Dispose() - { - if (_application is null) - { - return; - } - - _application.Dispose(); - } - - private void InitializeInternal() - { - while (_mainWindow is not { IsAvailable: true }) - { - try - { - _mainWindow = _application!.GetMainWindow(new UIA3Automation()); - } - catch { } - } - - GetDuration(); - GetSlider(); + InitializeInternal(Process); + return Task.CompletedTask; } - private void GetDuration() + private void InitializeInternal(Process process, bool hasCustomTitle = false) { - foreach (var item in _mainWindow!.FindAllDescendants(cf => cf.ByControlType(ControlType.Text))) - { - var match = DurationRegex().Match(item.Name); - if (match.Success && item.Patterns.LegacyIAccessible.Pattern.Description.Value.Contains("Total")) - { - var parts = item.Name.Split(':'); - - if (parts.Length == 3) - { - Duration = new TimeSpan(int.Parse(parts[0]), int.Parse(parts[1]), int.Parse(parts[2])); - } - else - { - Duration = new TimeSpan(0, int.Parse(parts[0]), int.Parse(parts[1])); - } - } - } + _interface = new(process); + + TitleChanged = hasCustomTitle + ? Observable.Return(_customTitle!) + : _interface.TitleChanged; + + DurationChanged = _interface.DurationChanged; + PositionChanged = _interface.PositionChanged; } - private void GetSlider() + public void Dispose() { - foreach (var item in _mainWindow!.FindAllDescendants(cf => cf.ByControlType(ControlType.Slider)).Select(x => x.AsSlider())) - { - if (!item.Patterns.LegacyIAccessible.Pattern.Description.Value.Contains('%')) - { - _slider = item; - var property = item.Patterns.Value.Pattern.PropertyIds.Value; - var handler = item.RegisterPropertyChangedEvent(TreeScope.Element, (ae, _, _) => - { - var percent = ae.AsSlider().Value; - _positionChanged.OnNext(TimeSpan.FromSeconds(Duration.TotalSeconds * percent / 10000)); - }, property); - } - } + _interface.Dispose(); } - - [GeneratedRegex(@"(\d)+:(\d)+")] - private static partial Regex DurationRegex(); } } diff --git a/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Vlc/Plugin.cs b/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Vlc/Plugin.cs index 7d1f22fe..cc455ca4 100644 --- a/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Vlc/Plugin.cs +++ b/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Vlc/Plugin.cs @@ -1,7 +1,6 @@ using System.Reflection; using Totoro.Plugins.Contracts; using Totoro.Plugins.MediaDetection.Contracts; -using Totoro.Plugins.Options; namespace Totoro.Plugins.MediaDetection.Vlc; @@ -16,12 +15,8 @@ public override PluginInfo GetInfo() Name = "vlc", DisplayName = "VLC Media Player", Description = "", - Version = Assembly.GetExecutingAssembly().GetName().Version! + Version = Assembly.GetExecutingAssembly().GetName().Version!, + Icon = typeof(Plugin).Assembly.GetManifestResourceStream("Totoro.Plugins.MediaDetection.Vlc.vlc_logo.png"), }; } } - -public class Config : ConfigObject -{ - public string FileName { get; set; } = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), @"VideoLAN\VLC\vlc.exe"); -} diff --git a/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Vlc/Totoro.Plugins.MediaDetection.Vlc.csproj b/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Vlc/Totoro.Plugins.MediaDetection.Vlc.csproj index be9bc985..b02b8020 100644 --- a/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Vlc/Totoro.Plugins.MediaDetection.Vlc.csproj +++ b/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Vlc/Totoro.Plugins.MediaDetection.Vlc.csproj @@ -5,12 +5,16 @@ enable enable x64 - 1.4 + 1.5 ..\..\..\Plugins Store\ false false + + + + diff --git a/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Vlc/vlc_logo.png b/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Vlc/vlc_logo.png new file mode 100644 index 00000000..45406bd7 Binary files /dev/null and b/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Vlc/vlc_logo.png differ diff --git a/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Win11MediaPlayer/MediaPlayer.cs b/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Win11MediaPlayer/MediaPlayer.cs index 7002cd27..fd2a0b49 100644 --- a/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Win11MediaPlayer/MediaPlayer.cs +++ b/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Win11MediaPlayer/MediaPlayer.cs @@ -1,29 +1,32 @@ using System.Diagnostics; using System.Reactive.Subjects; +using System.Windows.Forms; using FlaUI.Core.AutomationElements; +using ReactiveUI; using Totoro.Plugins.MediaDetection.Contracts; namespace Totoro.Plugins.MediaDetection.Win11MediaPlayer; -internal partial class MediaPlayer : INativeMediaPlayer +internal partial class MediaPlayer : ReactiveObject, INativeMediaPlayer { - private readonly Subject _positionChanged = new(); - private readonly Subject _durationChanged = new(); private Window? _mainWindow; - - public IObservable PositionChanged => _positionChanged; - public IObservable DurationChanged => _durationChanged; + public IObservable TitleChanged { get; } public Process? Process { get; private set; } - public TimeSpan Duration { get; set; } + public string Title { get; set; } = string.Empty; + + public MediaPlayer() + { + TitleChanged = this.WhenAnyValue(x => x.Title); + } public void Dispose() { } - public string GetTitle() + public Task GetTitle() { if (_mainWindow is null) { - return string.Empty; + return Task.FromResult(string.Empty); } var element = _mainWindow.FindFirstDescendant(cb => cb.ByAutomationId("mediaItemTitle")); @@ -32,13 +35,14 @@ public string GetTitle() element = _mainWindow.FindFirstDescendant(cb => cb.ByAutomationId("mediaItemTitle")); } - return element.Name; + return Task.FromResult(element.Name); } - public void Initialize(Window window) + public async Task Initialize(Window window) { _mainWindow = window; Process = Process.GetProcessesByName("Microsoft.Media.Player").First(); + Title = await GetTitle(); } } diff --git a/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Win11MediaPlayer/Plugin.cs b/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Win11MediaPlayer/Plugin.cs index 0f5c1b51..a4d70287 100644 --- a/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Win11MediaPlayer/Plugin.cs +++ b/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Win11MediaPlayer/Plugin.cs @@ -15,7 +15,8 @@ public PluginInfo GetInfo() Name = "Microsoft.Media.Player", DisplayName = "Windows Media Player", Description = "Default Windows 11 Media Player", - Version = Assembly.GetExecutingAssembly().GetName().Version! + Version = Assembly.GetExecutingAssembly().GetName().Version!, + Icon = typeof(Plugin).Assembly.GetManifestResourceStream("Totoro.Plugins.MediaDetection.Win11MediaPlayer.wmp-icon.png"), }; } diff --git a/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Win11MediaPlayer/Totoro.Plugins.MediaDetection.Win11MediaPlayer.csproj b/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Win11MediaPlayer/Totoro.Plugins.MediaDetection.Win11MediaPlayer.csproj index 1549b0a9..22bd5063 100644 --- a/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Win11MediaPlayer/Totoro.Plugins.MediaDetection.Win11MediaPlayer.csproj +++ b/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Win11MediaPlayer/Totoro.Plugins.MediaDetection.Win11MediaPlayer.csproj @@ -5,11 +5,15 @@ enable enable x64 - 1.3 + 1.4 ..\..\..\Plugins Store\ false false + + + + diff --git a/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Win11MediaPlayer/wmp-icon.png b/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Win11MediaPlayer/wmp-icon.png new file mode 100644 index 00000000..b6b4b9d3 Binary files /dev/null and b/Plugins/Media Detection/Totoro.Plugins.MediaDetection.Win11MediaPlayer/wmp-icon.png differ diff --git a/Totoro.Core.Tests/Builders/UserListViewModelBuilder.cs b/Totoro.Core.Tests/Builders/UserListViewModelBuilder.cs index cbfd93ec..8cb386b6 100644 --- a/Totoro.Core.Tests/Builders/UserListViewModelBuilder.cs +++ b/Totoro.Core.Tests/Builders/UserListViewModelBuilder.cs @@ -13,7 +13,6 @@ internal class UserListViewModelBuilder internal UserListViewModel Build() { return new UserListViewModel(_trackingServiceMock.Object, - _animeServiceMock.Object, _viewServiceMock.Object, Mock.Of(), _connectivityServiceMock.Object, diff --git a/Totoro.Core/Contracts/IAnimePreferencesService.cs b/Totoro.Core/Contracts/IAnimePreferencesService.cs new file mode 100644 index 00000000..4412b172 --- /dev/null +++ b/Totoro.Core/Contracts/IAnimePreferencesService.cs @@ -0,0 +1,10 @@ +namespace Totoro.Core.Contracts; + +public interface IAnimePreferencesService +{ + bool HasAlias(long id); + bool HasPreferences(long id); + AnimePreferences GetPreferences(long id); + void AddPreferences(long id, AnimePreferences preferences); + void Save(); +} diff --git a/Totoro.Core/Contracts/INameService.cs b/Totoro.Core/Contracts/INameService.cs deleted file mode 100644 index 2760b2e2..00000000 --- a/Totoro.Core/Contracts/INameService.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Totoro.Core.Contracts; - -public interface INameService -{ - bool HasName(long id); - string GetName(long id); - void AddOrUpdate(long id, string name); -} diff --git a/Totoro.Core/Contracts/IViewService.cs b/Totoro.Core/Contracts/IViewService.cs index 9ab53196..264002dd 100644 --- a/Totoro.Core/Contracts/IViewService.cs +++ b/Totoro.Core/Contracts/IViewService.cs @@ -17,6 +17,6 @@ public interface IViewService Task BrowseSubtitle(); Task UnhandledException(Exception ex); Task ShowPluginStore(string pluginType); - Task PromptAnimeName(long id); + Task PromptPreferences(long id); Task ShowSearchListServiceDialog(); } diff --git a/Totoro.Core/Models/AnimePreferences.cs b/Totoro.Core/Models/AnimePreferences.cs new file mode 100644 index 00000000..a2100028 --- /dev/null +++ b/Totoro.Core/Models/AnimePreferences.cs @@ -0,0 +1,11 @@ +using Totoro.Plugins.Anime.Models; + +namespace Totoro.Core.Models; + +public class AnimePreferences : ReactiveObject +{ + [Reactive] public string Alias { get; set; } + [Reactive] public string Provider { get; set; } + [Reactive] public StreamType StreamType { get; set; } = StreamType.Subbed(Languages.English); + [Reactive] public bool PreferDubs { get; set; } +} diff --git a/Totoro.Core/ServiceCollectionExtensions.cs b/Totoro.Core/ServiceCollectionExtensions.cs index 7137d20d..8ebeee41 100644 --- a/Totoro.Core/ServiceCollectionExtensions.cs +++ b/Totoro.Core/ServiceCollectionExtensions.cs @@ -40,7 +40,7 @@ public static IServiceCollection AddTotoro(this IServiceCollection services) services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddTransient(); diff --git a/Totoro.Core/Services/AnimePreferencesService.cs b/Totoro.Core/Services/AnimePreferencesService.cs new file mode 100644 index 00000000..4928e5a7 --- /dev/null +++ b/Totoro.Core/Services/AnimePreferencesService.cs @@ -0,0 +1,54 @@ +namespace Totoro.Core.Services; + + +public class AnimePreferencesService : IAnimePreferencesService +{ + private readonly Dictionary _maps; + private readonly ISettings _settings; + private readonly IKnownFolders _knownFolders; + private readonly IFileService _fileService; + private readonly string _fileName = @"anime-preferences.json"; + + public AnimePreferencesService(ISettings settings, + IKnownFolders knownFolders, + IFileService fileService) + { + _settings = settings; + _knownFolders = knownFolders; + _fileService = fileService; + + _maps = fileService.Read>(knownFolders.ApplicationData, _fileName) ?? []; + } + + public void AddPreferences(long id, AnimePreferences preferences) + { + _maps.Add(id, preferences); + } + + public AnimePreferences GetPreferences(long id) + { + return _maps[id]; + } + + public bool HasAlias(long id) + { + if (HasPreferences(id) == false) + { + return false; + } + + return !string.IsNullOrEmpty(GetPreferences(id).Alias); + } + + public bool HasPreferences(long id) + { + return _maps.ContainsKey(id); + } + + public void Save() + { + _fileService.Save(_knownFolders.ApplicationData, _fileName, _maps); + } +} + + diff --git a/Totoro.Core/Services/FileService.cs b/Totoro.Core/Services/FileService.cs index 3c682c85..5f5c365a 100644 --- a/Totoro.Core/Services/FileService.cs +++ b/Totoro.Core/Services/FileService.cs @@ -1,14 +1,13 @@ using System.Text; -using Newtonsoft.Json; namespace Totoro.Core.Services; public class FileService : IFileService { - private readonly JsonSerializerSettings _settings = new() + private readonly JsonSerializerOptions _settings = new() { - DefaultValueHandling = DefaultValueHandling.Ignore, - NullValueHandling = NullValueHandling.Ignore, + WriteIndented = true, + DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull }; public T Read(string folderPath, string fileName) @@ -17,7 +16,7 @@ public T Read(string folderPath, string fileName) if (File.Exists(path)) { var json = File.ReadAllText(path); - return JsonConvert.DeserializeObject(json); + return JsonSerializer.Deserialize(json); } return default; @@ -30,7 +29,7 @@ public void Save(string folderPath, string fileName, T content) Directory.CreateDirectory(folderPath); } - var fileContent = JsonConvert.SerializeObject(content, Formatting.Indented, _settings); + var fileContent = JsonSerializer.Serialize(content, _settings); File.WriteAllText(Path.Combine(folderPath, fileName), fileContent, Encoding.UTF8); } diff --git a/Totoro.Core/Services/INameService.cs b/Totoro.Core/Services/INameService.cs deleted file mode 100644 index 259b4453..00000000 --- a/Totoro.Core/Services/INameService.cs +++ /dev/null @@ -1,57 +0,0 @@ -namespace Totoro.Core.Services; -using ListServiceToNameMap = Dictionary>; - -public class NameService : INameService -{ - private readonly ListServiceToNameMap _maps; - private readonly ISettings _settings; - private readonly IKnownFolders _knownFolders; - private readonly IFileService _fileService; - private readonly string _fileName = @"names.json"; - - public NameService(ISettings settings, - IKnownFolders knownFolders, - IFileService fileService) - { - _settings = settings; - _knownFolders = knownFolders; - _fileService = fileService; - _maps = fileService.Read(knownFolders.ApplicationData, _fileName) ?? []; - } - - public void AddOrUpdate(long id, string name) - { - if (!_maps.TryGetValue(_settings.DefaultListService, out _)) - { - _maps[_settings.DefaultListService] = []; - } - - _maps[_settings.DefaultListService][id] = name; - Save(); - } - - public string GetName(long id) - { - if (!_maps.TryGetValue(_settings.DefaultListService, out var map)) - { - return string.Empty; - } - - return map[id]; - } - - public bool HasName(long id) - { - if (!_maps.TryGetValue(_settings.DefaultListService, out var map)) - { - return false; - } - - return map.ContainsKey(id); - } - - private void Save() - { - _fileService.Save(_knownFolders.ApplicationData, _fileName, _maps); - } -} diff --git a/Totoro.Core/TotoroCommands.cs b/Totoro.Core/TotoroCommands.cs index 0e89fba8..364f7f4b 100644 --- a/Totoro.Core/TotoroCommands.cs +++ b/Totoro.Core/TotoroCommands.cs @@ -38,14 +38,12 @@ public TotoroCommands(IViewService viewService, navigationService.NavigateTo(parameter: new Dictionary() { [WatchViewModelParamters.Anime] = anime, - [WatchViewModelParamters.Provider] = settings.DefaultProviderType }); break; case long id: navigationService.NavigateTo(parameter: new Dictionary() { [WatchViewModelParamters.AnimeId] = id, - [WatchViewModelParamters.Provider] = settings.DefaultProviderType }); break; case (AnimeModel anime, string providerType): @@ -193,7 +191,7 @@ public TotoroCommands(IViewService viewService, anime.Tracking = await trackingServiceContext.Update(anime.Id, Tracking.Previous(anime)); }); ShowPluginsStore = ReactiveCommand.CreateFromTask(viewService.ShowPluginStore); - SetName = ReactiveCommand.CreateFromTask(viewService.PromptAnimeName); + SetPreferences = ReactiveCommand.CreateFromTask(viewService.PromptPreferences); } public ICommand UpdateTracking { get; } @@ -213,7 +211,7 @@ public TotoroCommands(IViewService viewService, public ICommand IncrementTracking { get; } public ICommand DecrementTracking { get; } public ICommand ShowPluginsStore { get; } - public ICommand SetName { get; } + public ICommand SetPreferences { get; } public ICommand AddToPlanToWatch { get; } private async Task PlayYoutubeVideo(Video video, Func playVideo) diff --git a/Totoro.Core/ViewModels/WatchViewModel.cs b/Totoro.Core/ViewModels/WatchViewModel.cs index 93f0e1d9..a4a7130d 100644 --- a/Totoro.Core/ViewModels/WatchViewModel.cs +++ b/Totoro.Core/ViewModels/WatchViewModel.cs @@ -40,7 +40,7 @@ public partial class WatchViewModel : NavigatableViewModel private readonly IVideoStreamResolverFactory _videoStreamResolverFactory; private readonly ISimklService _simklService; private readonly IAnimeDetectionService _animeDetectionService; - private readonly INameService _nameService; + private readonly IAnimePreferencesService _preferencesService; private readonly List _mediaEventListeners; private readonly string[] _subDubProviders = ["gogo-anime", "anime-saturn"]; @@ -50,6 +50,7 @@ public partial class WatchViewModel : NavigatableViewModel private int? _episodeRequest; private IVideoStreamModelResolver _videoStreamResolver; private AnimeModel _anime; + private bool _canOverrideDefaultProvider; public WatchViewModel(IPluginFactory providerFactory, IViewService viewService, @@ -63,7 +64,7 @@ public WatchViewModel(IPluginFactory providerFactory, ISimklService simklService, IEnumerable mediaEventListeners, IAnimeDetectionService animeDetectionService, - INameService nameService) + IAnimePreferencesService preferencesService) { _providerFactory = providerFactory; _viewService = viewService; @@ -75,7 +76,7 @@ public WatchViewModel(IPluginFactory providerFactory, _videoStreamResolverFactory = videoStreamResolverFactory; _simklService = simklService; _animeDetectionService = animeDetectionService; - _nameService = nameService; + _preferencesService = preferencesService; _mediaEventListeners = mediaEventListeners.ToList(); NextEpisode = ReactiveCommand.Create(() => @@ -102,6 +103,7 @@ public WatchViewModel(IPluginFactory providerFactory, this.WhenAnyValue(x => x.ProviderType) .WhereNotNull() + .Log(this, "Provider Changed") .Subscribe(type => { Provider = _providerFactory.CreatePlugin(type); @@ -121,7 +123,10 @@ public WatchViewModel(IPluginFactory providerFactory, .Subscribe(async x => { var hasSubDub = x is { Dub: { }, Sub: { } }; - var searResult = _settings.PreferSubs ? x.Dub ?? x.Sub : x.Sub; + var preferDubs = _preferencesService.HasPreferences(Anime.Id) + ? _preferencesService.GetPreferences(Anime.Id).PreferDubs + : !_settings.PreferSubs; + var searResult = preferDubs ? x.Dub ?? x.Sub : x.Sub; if (hasSubDub) { @@ -130,7 +135,7 @@ public WatchViewModel(IPluginFactory providerFactory, SubStreams = ProviderType is "gogo-anime" ? [StreamType.Subbed(Languages.English), StreamType.Dubbed(Languages.English)] : [StreamType.Subbed(Languages.Italian), StreamType.Dubbed(Languages.Italian)]; - SelectedAudioStream = _settings.PreferSubs ? SubStreams.First() : SubStreams.Last(); + SelectedAudioStream = !preferDubs ? SubStreams.First() : SubStreams.Last(); } else { @@ -348,13 +353,16 @@ public override async Task OnNavigatedTo(IReadOnlyDictionary par parameters.ContainsKey(WatchViewModelParamters.TorrentManager); MediaPlayerType = UseTorrents ? Models.MediaPlayerType.FFMpeg : _settings.MediaPlayerType; ProviderType = parameters.GetValueOrDefault(WatchViewModelParamters.Provider, _settings.DefaultProviderType) as string; + _canOverrideDefaultProvider = !parameters.ContainsKey(WatchViewModelParamters.Provider); _providerOptions = _providerFactory.GetCurrentConfig(ProviderType); _isCrunchyroll = _settings.DefaultProviderType == "consumet" && _providerOptions.GetString("Provider", "zoro") == "crunchyroll"; SelectedAudioStream = GetDefaultAudioStream(); if (parameters.ContainsKey(WatchViewModelParamters.Anime)) { - Anime = parameters[WatchViewModelParamters.Anime] as AnimeModel; + var anime = parameters[WatchViewModelParamters.Anime] as AnimeModel; + UpdatePreferences(anime.Id); + Anime = anime; } else if (parameters.ContainsKey(WatchViewModelParamters.EpisodeInfo)) { @@ -366,6 +374,7 @@ public override async Task OnNavigatedTo(IReadOnlyDictionary par else if (parameters.ContainsKey(WatchViewModelParamters.AnimeId)) { var id = (long)parameters[WatchViewModelParamters.AnimeId]; + UpdatePreferences(id); Anime = await _animeService.GetInformation(id); } else if (parameters.ContainsKey(WatchViewModelParamters.SearchResult)) @@ -424,6 +433,50 @@ public override async Task OnNavigatedFrom() } } + private void UpdatePreferences(long id) + { + UpdatePreferedProvider(id); + UpdatePreferedAudioStream(id); + } + + private void UpdatePreferedProvider(long id) + { + if(!_canOverrideDefaultProvider) + { + return; + } + + if (!_preferencesService.HasPreferences(id)) + { + return; + } + + var preferences = _preferencesService.GetPreferences(id); + + if (string.IsNullOrEmpty(preferences.Provider)) + { + return; + } + + ProviderType = preferences.Provider; + } + + private void UpdatePreferedAudioStream(long id) + { + if (!_preferencesService.HasPreferences(id)) + { + return; + } + + if (_preferencesService.GetPreferences(id).PreferDubs) + { + if (ProviderType == "allanime") + { + SelectedAudioStream = StreamType.Dubbed(Languages.English); + } + } + } + private async Task<(ICatalogItem Sub, ICatalogItem Dub)> Find(long id, string title) { if (Provider is null) @@ -467,9 +520,9 @@ private void OnSubmitTimeStamps() private async Task<(ICatalogItem Sub, ICatalogItem Dub)> SearchProvider(long id, string title) { - if (_nameService.HasName(id)) + if (_preferencesService.HasAlias(id)) { - title = _nameService.GetName(id); + title = _preferencesService.GetPreferences(id).Alias; } var results = await Provider.Catalog.Search(title).ToListAsync(); @@ -572,6 +625,8 @@ private async Task TrySetAnime(string title) private void SetAnime(long id) { + UpdatePreferences(id); + _animeService.GetInformation(id) .Subscribe(async anime => { diff --git a/Totoro.Plugins.MediaDetection/Contracts/INativeMediaPlayer.cs b/Totoro.Plugins.MediaDetection/Contracts/INativeMediaPlayer.cs index 97b40d58..1cae5232 100644 --- a/Totoro.Plugins.MediaDetection/Contracts/INativeMediaPlayer.cs +++ b/Totoro.Plugins.MediaDetection/Contracts/INativeMediaPlayer.cs @@ -5,8 +5,8 @@ namespace Totoro.Plugins.MediaDetection.Contracts { public interface INativeMediaPlayer : IDisposable { - string GetTitle(); - void Initialize(Window window); + IObservable TitleChanged { get; } + Task Initialize(Window window); Process? Process { get; } } @@ -18,6 +18,6 @@ public interface IHavePosition public interface ICanLaunch { - void Launch(string title, string url); + Task Launch(string title, string url); } } diff --git a/Totoro.Plugins.MediaDetection/GenericMediaPlayer.cs b/Totoro.Plugins.MediaDetection/GenericMediaPlayer.cs index 02ea7d6c..6b3a0c8d 100644 --- a/Totoro.Plugins.MediaDetection/GenericMediaPlayer.cs +++ b/Totoro.Plugins.MediaDetection/GenericMediaPlayer.cs @@ -1,37 +1,48 @@ using System.Diagnostics; +using System.Reactive.Linq; using FlaUI.Core.AutomationElements; +using ReactiveUI; +using ReactiveUI.Fody.Helpers; using Totoro.Plugins.MediaDetection.Contracts; namespace Totoro.Plugins.MediaDetection; -public abstract class GenericMediaPlayer : INativeMediaPlayer +public abstract class GenericMediaPlayer : ReactiveObject, INativeMediaPlayer { public Process? Process { get; private set; } protected Window? _window; public bool GetTitleFromWindow { get; protected set; } = false; + [Reactive] public string Title { get; set; } = string.Empty; + public IObservable TitleChanged { get; } - public void Dispose() { } + public GenericMediaPlayer() + { + TitleChanged = this.WhenAnyValue(x => x.Title).DistinctUntilChanged(); + } - public virtual string GetTitle() + public virtual Task GetTitle() { if (GetTitleFromWindow && _window is not null) { - return _window.Title; + return Task.FromResult(_window.Title); } else if (Process is not null) { return ParseFromWindowTitle(Process.MainWindowTitle); } - return string.Empty; + return Task.FromResult(string.Empty); } - public void Initialize(Window window) + public async Task Initialize(Window window) { _window = window; Process = Process.GetProcessById(window.Properties.ProcessId); + Title = await GetTitle(); } - protected virtual string ParseFromWindowTitle(string windowTitle) => windowTitle; + protected virtual Task ParseFromWindowTitle(string windowTitle) => Task.FromResult(windowTitle); + + public abstract void Dispose(); } diff --git a/Totoro.Plugins.MediaDetection/ProcessWatcher.cs b/Totoro.Plugins.MediaDetection/ProcessWatcher.cs index 90202032..94fba3b7 100644 --- a/Totoro.Plugins.MediaDetection/ProcessWatcher.cs +++ b/Totoro.Plugins.MediaDetection/ProcessWatcher.cs @@ -25,7 +25,7 @@ public ProcessWatcher() { _desktop = _automation.GetDesktop(); _automationEventHandler = new(_desktop.FrameworkAutomationElement, _automation.EventLibrary.Window.WindowOpenedEvent, OnWindowOpened); - DetectMediaProcess(); + //DetectMediaProcess(); } public void Enable() diff --git a/Totoro.Plugins/Options/Glyphs.cs b/Totoro.Plugins/Options/Glyphs.cs index 8944384d..69964803 100644 --- a/Totoro.Plugins/Options/Glyphs.cs +++ b/Totoro.Plugins/Options/Glyphs.cs @@ -8,4 +8,8 @@ public class Glyphs public const string Sort = "\uE8CB"; public const string SortDirection = "\uF090"; public const string StreamType = "\uF2B7"; + public const string File = "\uEC50"; + public const string Password = "\uE9A8"; + public const string Website = "\uEB41"; + public const string Port = "\uF594"; } diff --git a/Totoro.WinUI/Activation/DefaultActivationHandler.cs b/Totoro.WinUI/Activation/DefaultActivationHandler.cs index 346dc2cd..52e1c522 100644 --- a/Totoro.WinUI/Activation/DefaultActivationHandler.cs +++ b/Totoro.WinUI/Activation/DefaultActivationHandler.cs @@ -4,6 +4,7 @@ using Totoro.Plugins; using Totoro.Plugins.Anime.Contracts; using Totoro.Plugins.MediaDetection; +using Totoro.Plugins.MediaDetection.Contracts; using Totoro.WinUI.Contracts; using Totoro.WinUI.ViewModels; @@ -16,6 +17,7 @@ public class DefaultActivationHandler : ActivationHandler + .Do(player => _detectedPlayer = player) + .SelectMany(player => player.TitleChanged) + .Subscribe(title => { - var title = player.GetTitle(); _mediaDetected = !settings.OnlyDetectMediaInLibraryFolders || - settings - .LibraryFolders - .Any(x => Directory.GetFiles(x, "*", SearchOption.AllDirectories) - .FirstOrDefault(x => x.Contains(player.GetTitle())) is not null); + settings + .LibraryFolders + .Any(x => Directory.GetFiles(x, "*", SearchOption.AllDirectories) + .FirstOrDefault(x => x.Contains(title)) is not null); foreach (var item in settings.LibraryFolders) { @@ -63,13 +66,13 @@ public DefaultActivationHandler(IWinUINavigationService navigationService, if (!_mediaDetected) { - processWatcher.RemoveProcess(player.Process); + processWatcher.RemoveProcess(_detectedPlayer.Process); return; } navigationService.NavigateTo(parameter: new Dictionary { - ["Player"] = player + ["Player"] = _detectedPlayer }); }); @@ -79,8 +82,8 @@ public DefaultActivationHandler(IWinUINavigationService navigationService, .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(_ => { - drpc.Clear(); navigationService.GoBack(); + drpc.Clear(); }); } diff --git a/Totoro.WinUI/Dialogs/ViewModels/EditAnimePreferencesViewModel.cs b/Totoro.WinUI/Dialogs/ViewModels/EditAnimePreferencesViewModel.cs new file mode 100644 index 00000000..dbdd5386 --- /dev/null +++ b/Totoro.WinUI/Dialogs/ViewModels/EditAnimePreferencesViewModel.cs @@ -0,0 +1,74 @@ +using Totoro.Plugins; +using Totoro.Plugins.Anime.Contracts; + +namespace Totoro.WinUI.Dialogs.ViewModels +{ + public class EditAnimePreferencesViewModel : DialogViewModel + { + private readonly IAnimePreferencesService _animePreferencesService; + private AnimePreferences _originalPreferences = null; + private long _id; + + public EditAnimePreferencesViewModel(IAnimePreferencesService animePreferencesService) + { + _animePreferencesService = animePreferencesService; + + SavePreferencesCommand = ReactiveCommand.Create(Save); + ResetProviderCommand = ReactiveCommand.Create(ResetProvider); + } + + public void Initialize(long id) + { + _id = id; + if(_animePreferencesService.HasPreferences(id)) + { + _originalPreferences = _animePreferencesService.GetPreferences(id); + Preferences = Clone(_originalPreferences); + } + else + { + Preferences = new(); + } + + } + + [Reactive] public AnimePreferences Preferences { get; set; } + public ICommand SavePreferencesCommand { get; } + public ICommand ResetProviderCommand { get; } + + public IEnumerable Providers => ["", ..PluginFactory.Instance.Plugins.Select(p => p.Name)]; + + private void Save() + { + if(_originalPreferences is null) + { + _animePreferencesService.AddPreferences(_id, Preferences); + } + else + { + _originalPreferences.Alias = Preferences.Alias; + _originalPreferences.StreamType = Preferences.StreamType; + _originalPreferences.Provider = Preferences.Provider; + _originalPreferences.PreferDubs = Preferences.PreferDubs; + } + + _animePreferencesService.Save(); + } + + private void ResetProvider() + { + Preferences.Provider = string.Empty; + } + + private static AnimePreferences Clone(AnimePreferences animePreferences) + { + return new AnimePreferences + { + Alias = animePreferences.Alias, + StreamType = animePreferences.StreamType, + Provider = animePreferences.Provider, + PreferDubs = animePreferences.PreferDubs + }; + } + } +} diff --git a/Totoro.WinUI/Dialogs/Views/EditAnimePreferencesView.xaml b/Totoro.WinUI/Dialogs/Views/EditAnimePreferencesView.xaml new file mode 100644 index 00000000..23a46e0a --- /dev/null +++ b/Totoro.WinUI/Dialogs/Views/EditAnimePreferencesView.xaml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Totoro.WinUI/Dialogs/Views/EditAnimePreferencesView.xaml.cs b/Totoro.WinUI/Dialogs/Views/EditAnimePreferencesView.xaml.cs new file mode 100644 index 00000000..ea6e1c5d --- /dev/null +++ b/Totoro.WinUI/Dialogs/Views/EditAnimePreferencesView.xaml.cs @@ -0,0 +1,14 @@ +using Totoro.WinUI.Dialogs.ViewModels; +using Totoro.WinUI.UserControls; + +namespace Totoro.WinUI.Dialogs.Views; + +public class EditAnimePreferencesViewBase : ReactiveContentDialog { } + +public sealed partial class EditAnimePreferencesView : EditAnimePreferencesViewBase +{ + public EditAnimePreferencesView() + { + InitializeComponent(); + } +} diff --git a/Totoro.WinUI/Helpers/Converters.cs b/Totoro.WinUI/Helpers/Converters.cs index 7120e82c..a43d08c2 100644 --- a/Totoro.WinUI/Helpers/Converters.cs +++ b/Totoro.WinUI/Helpers/Converters.cs @@ -168,10 +168,10 @@ public static MenuFlyout AnimeToFlyout(AnimeModel anime) flyout.Items.Add(new MenuFlyoutItem { - Text = @"Set Alternate Name", - Command = App.Commands.SetName, + Text = @"Set Preferences", + Command = App.Commands.SetPreferences, CommandParameter = anime.Id, - Icon = new FontIcon() { Glyph = "\uE8AC" } + Icon = new FontIcon() { Glyph = "\uEF58" } }); flyout.Items.Add(new MenuFlyoutItem { diff --git a/Totoro.WinUI/Helpers/ServiceCollectionExtensions.cs b/Totoro.WinUI/Helpers/ServiceCollectionExtensions.cs index 2079d741..2683e8ec 100644 --- a/Totoro.WinUI/Helpers/ServiceCollectionExtensions.cs +++ b/Totoro.WinUI/Helpers/ServiceCollectionExtensions.cs @@ -76,7 +76,7 @@ public static IServiceCollection AddMediaDetection(this IServiceCollection servi PluginFactory.Instance.LoadPlugin(new Plugins.MediaDetection.Vlc.Plugin()); PluginFactory.Instance.LoadPlugin(new Plugins.MediaDetection.Win11MediaPlayer.Plugin()); PluginFactory.Instance.LoadPlugin(new Plugins.MediaDetection.Generic.MpvPlugin()); - PluginFactory.Instance.LoadPlugin(new Plugins.MediaDetection.Generic.MpcHcPlugin()); + PluginFactory.Instance.LoadPlugin(new Plugins.MediaDetection.MpcHc.Plugin()); #endif return services; @@ -167,6 +167,7 @@ public static IServiceCollection AddDialogPages(this IServiceCollection services services.AddPage(); services.AddPage(); services.AddPage(); + services.AddPage(); return services; } diff --git a/Totoro.WinUI/Services/ViewService.cs b/Totoro.WinUI/Services/ViewService.cs index ea9c2622..b72692a6 100644 --- a/Totoro.WinUI/Services/ViewService.cs +++ b/Totoro.WinUI/Services/ViewService.cs @@ -20,12 +20,12 @@ namespace Totoro.WinUI.Services; public class ViewService(IContentDialogService contentDialogService, IAnimeServiceContext animeService, IToastService toastService, - INameService nameService) : IViewService, IEnableLogger + IAnimePreferencesService nameService) : IViewService, IEnableLogger { private readonly IContentDialogService _contentDialogService = contentDialogService; private readonly IAnimeServiceContext _animeService = animeService; private readonly IToastService _toastService = toastService; - private readonly INameService _nameService = nameService; + private readonly IAnimePreferencesService _nameService = nameService; private readonly ICommand _copyToClipboard = ReactiveCommand.Create(ex => { var package = new DataPackage(); @@ -33,7 +33,7 @@ public class ViewService(IContentDialogService contentDialogService, Clipboard.SetContent(package); }); - private static async Task ShowContentDialog(Action configure = null, bool allowMoving = true) + private static async Task<(TViewModel, ContentDialogResult)> ShowContentDialog(Action configure = null, bool allowMoving = true) where TViewModel: class, INotifyPropertyChanged { var view = App.GetService>(); @@ -59,8 +59,8 @@ private static async Task ShowContentDialog(Action UpdateTracking(IAnimeModel anime) @@ -95,7 +95,7 @@ public async Task RequestRating(IAnimeModel anime) public async Task ChoooseSearchResult(ICatalogItem closestMatch, List searchResults, string providerType) { - var viewModel = await ShowContentDialog(vm => + var (viewModel, _) = await ShowContentDialog(vm => { vm.SetValues(searchResults); vm.SelectedSearchResult = closestMatch; @@ -410,38 +410,8 @@ public async Task ShowSearchListServiceDialog() await ShowContentDialog(); } - public async Task PromptAnimeName(long id) + public async Task PromptPreferences(long id) { - var grid = new Grid(); - var textBox = new TextBox() - { - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Center - }; - grid.Children.Add(textBox); - - if (_nameService.HasName(id)) - { - textBox.Text = _nameService.GetName(id); - } - - var dialog = new ContentDialog() - { - Style = Application.Current.Resources["DefaultContentDialogStyle"] as Style, - XamlRoot = App.MainWindow.Content.XamlRoot, - DefaultButton = ContentDialogButton.Primary, - Content = grid, - ManipulationMode = Microsoft.UI.Xaml.Input.ManipulationModes.All, - Title = "Set Name", - PrimaryButtonText = "Save", - SecondaryButtonText = "Cancel" - }; - - var result = await dialog.ShowAsync(); - var text = textBox.Text; - if (result == ContentDialogResult.Primary && !string.IsNullOrEmpty(text)) - { - _nameService.AddOrUpdate(id, text); - } + await ShowContentDialog(vm => vm.Initialize(id)); } } diff --git a/Totoro.WinUI/Totoro.GUI.sln b/Totoro.WinUI/Totoro.GUI.sln index 5e3d5b22..8da19e40 100644 --- a/Totoro.WinUI/Totoro.GUI.sln +++ b/Totoro.WinUI/Totoro.GUI.sln @@ -93,142 +93,286 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Totoro.Plugins.Anime.Aniwav EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Totoro.Plugins.Anime.Aniwave.Tests", "..\Plugins\Anime\Totoro.Plugins.Anime.Aniwave.Tests\Totoro.Plugins.Anime.Aniwave.Tests.csproj", "{1955598A-A37F-4B3F-953F-7AC8D8770858}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Totoro.Plugins.MediaDetection.MpcHc", "..\Plugins\Media Detection\Totoro.Plugins.MediaDetection.MpcHc\Totoro.Plugins.MediaDetection.MpcHc.csproj", "{77FA0E72-8690-4433-BE41-179F506E2E51}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU Debug|x64 = Debug|x64 + Release|Any CPU = Release|Any CPU Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {346FD672-F5D5-4226-A467-EFEBDBAB4BDA}.Debug|Any CPU.ActiveCfg = Debug|x64 + {346FD672-F5D5-4226-A467-EFEBDBAB4BDA}.Debug|Any CPU.Build.0 = Debug|x64 {346FD672-F5D5-4226-A467-EFEBDBAB4BDA}.Debug|x64.ActiveCfg = Debug|x64 {346FD672-F5D5-4226-A467-EFEBDBAB4BDA}.Debug|x64.Build.0 = Debug|x64 {346FD672-F5D5-4226-A467-EFEBDBAB4BDA}.Debug|x64.Deploy.0 = Debug|x64 + {346FD672-F5D5-4226-A467-EFEBDBAB4BDA}.Release|Any CPU.ActiveCfg = Release|x64 + {346FD672-F5D5-4226-A467-EFEBDBAB4BDA}.Release|Any CPU.Build.0 = Release|x64 {346FD672-F5D5-4226-A467-EFEBDBAB4BDA}.Release|x64.ActiveCfg = Release|x64 {346FD672-F5D5-4226-A467-EFEBDBAB4BDA}.Release|x64.Build.0 = Release|x64 {346FD672-F5D5-4226-A467-EFEBDBAB4BDA}.Release|x64.Deploy.0 = Release|x64 + {39FA00D8-59A7-4167-9015-BFEE0EEB6E14}.Debug|Any CPU.ActiveCfg = Debug|x64 + {39FA00D8-59A7-4167-9015-BFEE0EEB6E14}.Debug|Any CPU.Build.0 = Debug|x64 {39FA00D8-59A7-4167-9015-BFEE0EEB6E14}.Debug|x64.ActiveCfg = Debug|x64 + {39FA00D8-59A7-4167-9015-BFEE0EEB6E14}.Release|Any CPU.ActiveCfg = Release|x64 + {39FA00D8-59A7-4167-9015-BFEE0EEB6E14}.Release|Any CPU.Build.0 = Release|x64 {39FA00D8-59A7-4167-9015-BFEE0EEB6E14}.Release|x64.ActiveCfg = Release|x64 + {0B78252D-1C5D-446A-84DE-D0B17418218A}.Debug|Any CPU.ActiveCfg = Debug|x86 + {0B78252D-1C5D-446A-84DE-D0B17418218A}.Debug|Any CPU.Build.0 = Debug|x86 {0B78252D-1C5D-446A-84DE-D0B17418218A}.Debug|x64.ActiveCfg = Debug|x86 + {0B78252D-1C5D-446A-84DE-D0B17418218A}.Release|Any CPU.ActiveCfg = Release|x86 + {0B78252D-1C5D-446A-84DE-D0B17418218A}.Release|Any CPU.Build.0 = Release|x86 {0B78252D-1C5D-446A-84DE-D0B17418218A}.Release|x64.ActiveCfg = Release|x86 + {0DC35921-32A4-42EC-A293-8916797EB358}.Debug|Any CPU.ActiveCfg = Debug|x64 + {0DC35921-32A4-42EC-A293-8916797EB358}.Debug|Any CPU.Build.0 = Debug|x64 {0DC35921-32A4-42EC-A293-8916797EB358}.Debug|x64.ActiveCfg = Debug|x64 {0DC35921-32A4-42EC-A293-8916797EB358}.Debug|x64.Build.0 = Debug|x64 + {0DC35921-32A4-42EC-A293-8916797EB358}.Release|Any CPU.ActiveCfg = Release|x64 + {0DC35921-32A4-42EC-A293-8916797EB358}.Release|Any CPU.Build.0 = Release|x64 {0DC35921-32A4-42EC-A293-8916797EB358}.Release|x64.ActiveCfg = Release|x64 {0DC35921-32A4-42EC-A293-8916797EB358}.Release|x64.Build.0 = Release|x64 + {53A39CBF-704F-4940-B008-83B83972B250}.Debug|Any CPU.ActiveCfg = Debug|x64 + {53A39CBF-704F-4940-B008-83B83972B250}.Debug|Any CPU.Build.0 = Debug|x64 {53A39CBF-704F-4940-B008-83B83972B250}.Debug|x64.ActiveCfg = Debug|x64 {53A39CBF-704F-4940-B008-83B83972B250}.Debug|x64.Build.0 = Debug|x64 + {53A39CBF-704F-4940-B008-83B83972B250}.Release|Any CPU.ActiveCfg = Release|x64 + {53A39CBF-704F-4940-B008-83B83972B250}.Release|Any CPU.Build.0 = Release|x64 {53A39CBF-704F-4940-B008-83B83972B250}.Release|x64.ActiveCfg = Release|x64 {53A39CBF-704F-4940-B008-83B83972B250}.Release|x64.Build.0 = Release|x64 + {F150D2AF-D88B-43B0-87FA-75BC04253E30}.Debug|Any CPU.ActiveCfg = Debug|x64 + {F150D2AF-D88B-43B0-87FA-75BC04253E30}.Debug|Any CPU.Build.0 = Debug|x64 {F150D2AF-D88B-43B0-87FA-75BC04253E30}.Debug|x64.ActiveCfg = Debug|x64 {F150D2AF-D88B-43B0-87FA-75BC04253E30}.Debug|x64.Build.0 = Debug|x64 + {F150D2AF-D88B-43B0-87FA-75BC04253E30}.Release|Any CPU.ActiveCfg = Release|x64 + {F150D2AF-D88B-43B0-87FA-75BC04253E30}.Release|Any CPU.Build.0 = Release|x64 {F150D2AF-D88B-43B0-87FA-75BC04253E30}.Release|x64.ActiveCfg = Release|x64 {F150D2AF-D88B-43B0-87FA-75BC04253E30}.Release|x64.Build.0 = Release|x64 + {D0B38240-41C6-4CE2-8E70-6E237FF65498}.Debug|Any CPU.ActiveCfg = Debug|x64 + {D0B38240-41C6-4CE2-8E70-6E237FF65498}.Debug|Any CPU.Build.0 = Debug|x64 {D0B38240-41C6-4CE2-8E70-6E237FF65498}.Debug|x64.ActiveCfg = Debug|x64 {D0B38240-41C6-4CE2-8E70-6E237FF65498}.Debug|x64.Build.0 = Debug|x64 + {D0B38240-41C6-4CE2-8E70-6E237FF65498}.Release|Any CPU.ActiveCfg = Release|x64 + {D0B38240-41C6-4CE2-8E70-6E237FF65498}.Release|Any CPU.Build.0 = Release|x64 {D0B38240-41C6-4CE2-8E70-6E237FF65498}.Release|x64.ActiveCfg = Release|x64 {D0B38240-41C6-4CE2-8E70-6E237FF65498}.Release|x64.Build.0 = Release|x64 + {84C78515-F87D-406B-955E-AE84CDC8A44E}.Debug|Any CPU.ActiveCfg = Debug|x64 + {84C78515-F87D-406B-955E-AE84CDC8A44E}.Debug|Any CPU.Build.0 = Debug|x64 {84C78515-F87D-406B-955E-AE84CDC8A44E}.Debug|x64.ActiveCfg = Debug|x64 {84C78515-F87D-406B-955E-AE84CDC8A44E}.Debug|x64.Build.0 = Debug|x64 + {84C78515-F87D-406B-955E-AE84CDC8A44E}.Release|Any CPU.ActiveCfg = Release|x64 + {84C78515-F87D-406B-955E-AE84CDC8A44E}.Release|Any CPU.Build.0 = Release|x64 {84C78515-F87D-406B-955E-AE84CDC8A44E}.Release|x64.ActiveCfg = Release|x64 {84C78515-F87D-406B-955E-AE84CDC8A44E}.Release|x64.Build.0 = Release|x64 + {2FE52236-DF8B-40C6-96FF-914434118044}.Debug|Any CPU.ActiveCfg = Debug|x64 + {2FE52236-DF8B-40C6-96FF-914434118044}.Debug|Any CPU.Build.0 = Debug|x64 {2FE52236-DF8B-40C6-96FF-914434118044}.Debug|x64.ActiveCfg = Debug|x64 {2FE52236-DF8B-40C6-96FF-914434118044}.Debug|x64.Build.0 = Debug|x64 + {2FE52236-DF8B-40C6-96FF-914434118044}.Release|Any CPU.ActiveCfg = Release|x64 + {2FE52236-DF8B-40C6-96FF-914434118044}.Release|Any CPU.Build.0 = Release|x64 {2FE52236-DF8B-40C6-96FF-914434118044}.Release|x64.ActiveCfg = Release|x64 {2FE52236-DF8B-40C6-96FF-914434118044}.Release|x64.Build.0 = Release|x64 + {235E5957-AC87-4323-8E3B-64A948BB6621}.Debug|Any CPU.ActiveCfg = Debug|x64 + {235E5957-AC87-4323-8E3B-64A948BB6621}.Debug|Any CPU.Build.0 = Debug|x64 {235E5957-AC87-4323-8E3B-64A948BB6621}.Debug|x64.ActiveCfg = Debug|x64 {235E5957-AC87-4323-8E3B-64A948BB6621}.Debug|x64.Build.0 = Debug|x64 + {235E5957-AC87-4323-8E3B-64A948BB6621}.Release|Any CPU.ActiveCfg = Release|x64 + {235E5957-AC87-4323-8E3B-64A948BB6621}.Release|Any CPU.Build.0 = Release|x64 {235E5957-AC87-4323-8E3B-64A948BB6621}.Release|x64.ActiveCfg = Release|x64 {235E5957-AC87-4323-8E3B-64A948BB6621}.Release|x64.Build.0 = Release|x64 + {B727DAB9-0169-4733-9CB3-CB75BC8F9FA6}.Debug|Any CPU.ActiveCfg = Debug|x64 + {B727DAB9-0169-4733-9CB3-CB75BC8F9FA6}.Debug|Any CPU.Build.0 = Debug|x64 {B727DAB9-0169-4733-9CB3-CB75BC8F9FA6}.Debug|x64.ActiveCfg = Debug|x64 {B727DAB9-0169-4733-9CB3-CB75BC8F9FA6}.Debug|x64.Build.0 = Debug|x64 + {B727DAB9-0169-4733-9CB3-CB75BC8F9FA6}.Release|Any CPU.ActiveCfg = Release|x64 + {B727DAB9-0169-4733-9CB3-CB75BC8F9FA6}.Release|Any CPU.Build.0 = Release|x64 {B727DAB9-0169-4733-9CB3-CB75BC8F9FA6}.Release|x64.ActiveCfg = Release|x64 {B727DAB9-0169-4733-9CB3-CB75BC8F9FA6}.Release|x64.Build.0 = Release|x64 + {F2D03497-DCC2-4FC8-AF03-60B316ACEB62}.Debug|Any CPU.ActiveCfg = Debug|x64 + {F2D03497-DCC2-4FC8-AF03-60B316ACEB62}.Debug|Any CPU.Build.0 = Debug|x64 {F2D03497-DCC2-4FC8-AF03-60B316ACEB62}.Debug|x64.ActiveCfg = Debug|x64 {F2D03497-DCC2-4FC8-AF03-60B316ACEB62}.Debug|x64.Build.0 = Debug|x64 + {F2D03497-DCC2-4FC8-AF03-60B316ACEB62}.Release|Any CPU.ActiveCfg = Release|x64 + {F2D03497-DCC2-4FC8-AF03-60B316ACEB62}.Release|Any CPU.Build.0 = Release|x64 {F2D03497-DCC2-4FC8-AF03-60B316ACEB62}.Release|x64.ActiveCfg = Release|x64 {F2D03497-DCC2-4FC8-AF03-60B316ACEB62}.Release|x64.Build.0 = Release|x64 + {C4E006E2-7A89-4E80-8F54-9D5391BE2D14}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C4E006E2-7A89-4E80-8F54-9D5391BE2D14}.Debug|Any CPU.Build.0 = Debug|Any CPU {C4E006E2-7A89-4E80-8F54-9D5391BE2D14}.Debug|x64.ActiveCfg = Debug|x64 {C4E006E2-7A89-4E80-8F54-9D5391BE2D14}.Debug|x64.Build.0 = Debug|x64 + {C4E006E2-7A89-4E80-8F54-9D5391BE2D14}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C4E006E2-7A89-4E80-8F54-9D5391BE2D14}.Release|Any CPU.Build.0 = Release|Any CPU {C4E006E2-7A89-4E80-8F54-9D5391BE2D14}.Release|x64.ActiveCfg = Release|x64 {C4E006E2-7A89-4E80-8F54-9D5391BE2D14}.Release|x64.Build.0 = Release|x64 + {282BACC8-47D0-4923-9CBA-E499E68CA81B}.Debug|Any CPU.ActiveCfg = Debug|x64 + {282BACC8-47D0-4923-9CBA-E499E68CA81B}.Debug|Any CPU.Build.0 = Debug|x64 {282BACC8-47D0-4923-9CBA-E499E68CA81B}.Debug|x64.ActiveCfg = Debug|x64 {282BACC8-47D0-4923-9CBA-E499E68CA81B}.Debug|x64.Build.0 = Debug|x64 + {282BACC8-47D0-4923-9CBA-E499E68CA81B}.Release|Any CPU.ActiveCfg = Release|x64 + {282BACC8-47D0-4923-9CBA-E499E68CA81B}.Release|Any CPU.Build.0 = Release|x64 {282BACC8-47D0-4923-9CBA-E499E68CA81B}.Release|x64.ActiveCfg = Release|x64 {282BACC8-47D0-4923-9CBA-E499E68CA81B}.Release|x64.Build.0 = Release|x64 + {0303576B-6099-416C-8F32-F7A131E8EDFD}.Debug|Any CPU.ActiveCfg = Debug|x64 + {0303576B-6099-416C-8F32-F7A131E8EDFD}.Debug|Any CPU.Build.0 = Debug|x64 {0303576B-6099-416C-8F32-F7A131E8EDFD}.Debug|x64.ActiveCfg = Debug|x64 {0303576B-6099-416C-8F32-F7A131E8EDFD}.Debug|x64.Build.0 = Debug|x64 + {0303576B-6099-416C-8F32-F7A131E8EDFD}.Release|Any CPU.ActiveCfg = Release|x64 + {0303576B-6099-416C-8F32-F7A131E8EDFD}.Release|Any CPU.Build.0 = Release|x64 {0303576B-6099-416C-8F32-F7A131E8EDFD}.Release|x64.ActiveCfg = Release|x64 {0303576B-6099-416C-8F32-F7A131E8EDFD}.Release|x64.Build.0 = Release|x64 + {3527BAD8-2A4A-492C-9DEB-37205A445C45}.Debug|Any CPU.ActiveCfg = Debug|x64 + {3527BAD8-2A4A-492C-9DEB-37205A445C45}.Debug|Any CPU.Build.0 = Debug|x64 {3527BAD8-2A4A-492C-9DEB-37205A445C45}.Debug|x64.ActiveCfg = Debug|x64 {3527BAD8-2A4A-492C-9DEB-37205A445C45}.Debug|x64.Build.0 = Debug|x64 + {3527BAD8-2A4A-492C-9DEB-37205A445C45}.Release|Any CPU.ActiveCfg = Release|x64 + {3527BAD8-2A4A-492C-9DEB-37205A445C45}.Release|Any CPU.Build.0 = Release|x64 {3527BAD8-2A4A-492C-9DEB-37205A445C45}.Release|x64.ActiveCfg = Release|x64 {3527BAD8-2A4A-492C-9DEB-37205A445C45}.Release|x64.Build.0 = Release|x64 + {03A01D57-2E2F-4D8F-B462-570AB1878925}.Debug|Any CPU.ActiveCfg = Debug|x64 + {03A01D57-2E2F-4D8F-B462-570AB1878925}.Debug|Any CPU.Build.0 = Debug|x64 {03A01D57-2E2F-4D8F-B462-570AB1878925}.Debug|x64.ActiveCfg = Debug|x64 {03A01D57-2E2F-4D8F-B462-570AB1878925}.Debug|x64.Build.0 = Debug|x64 + {03A01D57-2E2F-4D8F-B462-570AB1878925}.Release|Any CPU.ActiveCfg = Release|x64 + {03A01D57-2E2F-4D8F-B462-570AB1878925}.Release|Any CPU.Build.0 = Release|x64 {03A01D57-2E2F-4D8F-B462-570AB1878925}.Release|x64.ActiveCfg = Release|x64 {03A01D57-2E2F-4D8F-B462-570AB1878925}.Release|x64.Build.0 = Release|x64 + {7FEB2745-E922-43FA-9141-4E37D93DDD81}.Debug|Any CPU.ActiveCfg = Debug|x64 + {7FEB2745-E922-43FA-9141-4E37D93DDD81}.Debug|Any CPU.Build.0 = Debug|x64 {7FEB2745-E922-43FA-9141-4E37D93DDD81}.Debug|x64.ActiveCfg = Debug|x64 {7FEB2745-E922-43FA-9141-4E37D93DDD81}.Debug|x64.Build.0 = Debug|x64 + {7FEB2745-E922-43FA-9141-4E37D93DDD81}.Release|Any CPU.ActiveCfg = Release|x64 + {7FEB2745-E922-43FA-9141-4E37D93DDD81}.Release|Any CPU.Build.0 = Release|x64 {7FEB2745-E922-43FA-9141-4E37D93DDD81}.Release|x64.ActiveCfg = Release|x64 {7FEB2745-E922-43FA-9141-4E37D93DDD81}.Release|x64.Build.0 = Release|x64 + {3071EFF8-C1DA-4C48-AC41-01BB468AC9B7}.Debug|Any CPU.ActiveCfg = Debug|x64 + {3071EFF8-C1DA-4C48-AC41-01BB468AC9B7}.Debug|Any CPU.Build.0 = Debug|x64 {3071EFF8-C1DA-4C48-AC41-01BB468AC9B7}.Debug|x64.ActiveCfg = Debug|x64 {3071EFF8-C1DA-4C48-AC41-01BB468AC9B7}.Debug|x64.Build.0 = Debug|x64 + {3071EFF8-C1DA-4C48-AC41-01BB468AC9B7}.Release|Any CPU.ActiveCfg = Release|x64 + {3071EFF8-C1DA-4C48-AC41-01BB468AC9B7}.Release|Any CPU.Build.0 = Release|x64 {3071EFF8-C1DA-4C48-AC41-01BB468AC9B7}.Release|x64.ActiveCfg = Release|x64 {3071EFF8-C1DA-4C48-AC41-01BB468AC9B7}.Release|x64.Build.0 = Release|x64 + {0399ED4B-3BC0-4799-B6A0-F3448EABD212}.Debug|Any CPU.ActiveCfg = Debug|x64 + {0399ED4B-3BC0-4799-B6A0-F3448EABD212}.Debug|Any CPU.Build.0 = Debug|x64 {0399ED4B-3BC0-4799-B6A0-F3448EABD212}.Debug|x64.ActiveCfg = Debug|x64 {0399ED4B-3BC0-4799-B6A0-F3448EABD212}.Debug|x64.Build.0 = Debug|x64 + {0399ED4B-3BC0-4799-B6A0-F3448EABD212}.Release|Any CPU.ActiveCfg = Release|x64 + {0399ED4B-3BC0-4799-B6A0-F3448EABD212}.Release|Any CPU.Build.0 = Release|x64 {0399ED4B-3BC0-4799-B6A0-F3448EABD212}.Release|x64.ActiveCfg = Release|x64 {0399ED4B-3BC0-4799-B6A0-F3448EABD212}.Release|x64.Build.0 = Release|x64 + {06099802-65BD-47D6-AB96-F8D65654012F}.Debug|Any CPU.ActiveCfg = Debug|x64 + {06099802-65BD-47D6-AB96-F8D65654012F}.Debug|Any CPU.Build.0 = Debug|x64 {06099802-65BD-47D6-AB96-F8D65654012F}.Debug|x64.ActiveCfg = Debug|x64 {06099802-65BD-47D6-AB96-F8D65654012F}.Debug|x64.Build.0 = Debug|x64 + {06099802-65BD-47D6-AB96-F8D65654012F}.Release|Any CPU.ActiveCfg = Release|x64 + {06099802-65BD-47D6-AB96-F8D65654012F}.Release|Any CPU.Build.0 = Release|x64 {06099802-65BD-47D6-AB96-F8D65654012F}.Release|x64.ActiveCfg = Release|x64 {06099802-65BD-47D6-AB96-F8D65654012F}.Release|x64.Build.0 = Release|x64 + {53AF97EB-94A1-4AC1-9946-73AAB26C8A1B}.Debug|Any CPU.ActiveCfg = Debug|x64 + {53AF97EB-94A1-4AC1-9946-73AAB26C8A1B}.Debug|Any CPU.Build.0 = Debug|x64 {53AF97EB-94A1-4AC1-9946-73AAB26C8A1B}.Debug|x64.ActiveCfg = Debug|x64 {53AF97EB-94A1-4AC1-9946-73AAB26C8A1B}.Debug|x64.Build.0 = Debug|x64 + {53AF97EB-94A1-4AC1-9946-73AAB26C8A1B}.Release|Any CPU.ActiveCfg = Release|x64 + {53AF97EB-94A1-4AC1-9946-73AAB26C8A1B}.Release|Any CPU.Build.0 = Release|x64 {53AF97EB-94A1-4AC1-9946-73AAB26C8A1B}.Release|x64.ActiveCfg = Release|x64 {53AF97EB-94A1-4AC1-9946-73AAB26C8A1B}.Release|x64.Build.0 = Release|x64 + {5117D9CF-5D78-459F-9546-113BCEDD79B8}.Debug|Any CPU.ActiveCfg = Debug|x64 + {5117D9CF-5D78-459F-9546-113BCEDD79B8}.Debug|Any CPU.Build.0 = Debug|x64 {5117D9CF-5D78-459F-9546-113BCEDD79B8}.Debug|x64.ActiveCfg = Debug|x64 {5117D9CF-5D78-459F-9546-113BCEDD79B8}.Debug|x64.Build.0 = Debug|x64 + {5117D9CF-5D78-459F-9546-113BCEDD79B8}.Release|Any CPU.ActiveCfg = Release|x64 + {5117D9CF-5D78-459F-9546-113BCEDD79B8}.Release|Any CPU.Build.0 = Release|x64 {5117D9CF-5D78-459F-9546-113BCEDD79B8}.Release|x64.ActiveCfg = Release|x64 {5117D9CF-5D78-459F-9546-113BCEDD79B8}.Release|x64.Build.0 = Release|x64 + {A2856FA8-E5E3-469D-997B-CC50E6B4BC07}.Debug|Any CPU.ActiveCfg = Debug|x64 + {A2856FA8-E5E3-469D-997B-CC50E6B4BC07}.Debug|Any CPU.Build.0 = Debug|x64 {A2856FA8-E5E3-469D-997B-CC50E6B4BC07}.Debug|x64.ActiveCfg = Debug|x64 {A2856FA8-E5E3-469D-997B-CC50E6B4BC07}.Debug|x64.Build.0 = Debug|x64 + {A2856FA8-E5E3-469D-997B-CC50E6B4BC07}.Release|Any CPU.ActiveCfg = Release|x64 + {A2856FA8-E5E3-469D-997B-CC50E6B4BC07}.Release|Any CPU.Build.0 = Release|x64 {A2856FA8-E5E3-469D-997B-CC50E6B4BC07}.Release|x64.ActiveCfg = Release|x64 {A2856FA8-E5E3-469D-997B-CC50E6B4BC07}.Release|x64.Build.0 = Release|x64 + {CCCBA99C-8ACB-4873-9DC8-D99EA3661E0D}.Debug|Any CPU.ActiveCfg = Debug|x64 + {CCCBA99C-8ACB-4873-9DC8-D99EA3661E0D}.Debug|Any CPU.Build.0 = Debug|x64 {CCCBA99C-8ACB-4873-9DC8-D99EA3661E0D}.Debug|x64.ActiveCfg = Debug|x64 {CCCBA99C-8ACB-4873-9DC8-D99EA3661E0D}.Debug|x64.Build.0 = Debug|x64 + {CCCBA99C-8ACB-4873-9DC8-D99EA3661E0D}.Release|Any CPU.ActiveCfg = Release|x64 + {CCCBA99C-8ACB-4873-9DC8-D99EA3661E0D}.Release|Any CPU.Build.0 = Release|x64 {CCCBA99C-8ACB-4873-9DC8-D99EA3661E0D}.Release|x64.ActiveCfg = Release|x64 {CCCBA99C-8ACB-4873-9DC8-D99EA3661E0D}.Release|x64.Build.0 = Release|x64 + {EF158ADE-DD82-4C5D-8EED-E6170C28B238}.Debug|Any CPU.ActiveCfg = Debug|x64 + {EF158ADE-DD82-4C5D-8EED-E6170C28B238}.Debug|Any CPU.Build.0 = Debug|x64 {EF158ADE-DD82-4C5D-8EED-E6170C28B238}.Debug|x64.ActiveCfg = Debug|x64 {EF158ADE-DD82-4C5D-8EED-E6170C28B238}.Debug|x64.Build.0 = Debug|x64 + {EF158ADE-DD82-4C5D-8EED-E6170C28B238}.Release|Any CPU.ActiveCfg = Release|x64 + {EF158ADE-DD82-4C5D-8EED-E6170C28B238}.Release|Any CPU.Build.0 = Release|x64 {EF158ADE-DD82-4C5D-8EED-E6170C28B238}.Release|x64.ActiveCfg = Release|x64 {EF158ADE-DD82-4C5D-8EED-E6170C28B238}.Release|x64.Build.0 = Release|x64 + {D6326042-EE8D-426B-9DE1-0BBD724FF029}.Debug|Any CPU.ActiveCfg = Debug|x64 + {D6326042-EE8D-426B-9DE1-0BBD724FF029}.Debug|Any CPU.Build.0 = Debug|x64 {D6326042-EE8D-426B-9DE1-0BBD724FF029}.Debug|x64.ActiveCfg = Debug|x64 {D6326042-EE8D-426B-9DE1-0BBD724FF029}.Debug|x64.Build.0 = Debug|x64 + {D6326042-EE8D-426B-9DE1-0BBD724FF029}.Release|Any CPU.ActiveCfg = Release|x64 + {D6326042-EE8D-426B-9DE1-0BBD724FF029}.Release|Any CPU.Build.0 = Release|x64 {D6326042-EE8D-426B-9DE1-0BBD724FF029}.Release|x64.ActiveCfg = Release|x64 {D6326042-EE8D-426B-9DE1-0BBD724FF029}.Release|x64.Build.0 = Release|x64 + {4B042113-D218-4018-B1AD-C67E57F6500D}.Debug|Any CPU.ActiveCfg = Debug|x64 + {4B042113-D218-4018-B1AD-C67E57F6500D}.Debug|Any CPU.Build.0 = Debug|x64 {4B042113-D218-4018-B1AD-C67E57F6500D}.Debug|x64.ActiveCfg = Debug|x64 {4B042113-D218-4018-B1AD-C67E57F6500D}.Debug|x64.Build.0 = Debug|x64 + {4B042113-D218-4018-B1AD-C67E57F6500D}.Release|Any CPU.ActiveCfg = Release|x64 + {4B042113-D218-4018-B1AD-C67E57F6500D}.Release|Any CPU.Build.0 = Release|x64 {4B042113-D218-4018-B1AD-C67E57F6500D}.Release|x64.ActiveCfg = Release|x64 {4B042113-D218-4018-B1AD-C67E57F6500D}.Release|x64.Build.0 = Release|x64 + {520ADEA3-B0ED-42CB-B5A9-D31700391BB1}.Debug|Any CPU.ActiveCfg = Debug|x64 + {520ADEA3-B0ED-42CB-B5A9-D31700391BB1}.Debug|Any CPU.Build.0 = Debug|x64 {520ADEA3-B0ED-42CB-B5A9-D31700391BB1}.Debug|x64.ActiveCfg = Debug|x64 {520ADEA3-B0ED-42CB-B5A9-D31700391BB1}.Debug|x64.Build.0 = Debug|x64 + {520ADEA3-B0ED-42CB-B5A9-D31700391BB1}.Release|Any CPU.ActiveCfg = Release|x64 + {520ADEA3-B0ED-42CB-B5A9-D31700391BB1}.Release|Any CPU.Build.0 = Release|x64 {520ADEA3-B0ED-42CB-B5A9-D31700391BB1}.Release|x64.ActiveCfg = Release|x64 {520ADEA3-B0ED-42CB-B5A9-D31700391BB1}.Release|x64.Build.0 = Release|x64 + {2D423284-85B6-4C57-B8CE-AC62DC3EBD75}.Debug|Any CPU.ActiveCfg = Debug|x64 + {2D423284-85B6-4C57-B8CE-AC62DC3EBD75}.Debug|Any CPU.Build.0 = Debug|x64 {2D423284-85B6-4C57-B8CE-AC62DC3EBD75}.Debug|x64.ActiveCfg = Debug|x64 {2D423284-85B6-4C57-B8CE-AC62DC3EBD75}.Debug|x64.Build.0 = Debug|x64 + {2D423284-85B6-4C57-B8CE-AC62DC3EBD75}.Release|Any CPU.ActiveCfg = Release|x64 + {2D423284-85B6-4C57-B8CE-AC62DC3EBD75}.Release|Any CPU.Build.0 = Release|x64 {2D423284-85B6-4C57-B8CE-AC62DC3EBD75}.Release|x64.ActiveCfg = Release|x64 {2D423284-85B6-4C57-B8CE-AC62DC3EBD75}.Release|x64.Build.0 = Release|x64 + {CC7B1FE4-CF75-45EC-9B64-6AE500D81E8C}.Debug|Any CPU.ActiveCfg = Debug|x64 + {CC7B1FE4-CF75-45EC-9B64-6AE500D81E8C}.Debug|Any CPU.Build.0 = Debug|x64 {CC7B1FE4-CF75-45EC-9B64-6AE500D81E8C}.Debug|x64.ActiveCfg = Debug|x64 {CC7B1FE4-CF75-45EC-9B64-6AE500D81E8C}.Debug|x64.Build.0 = Debug|x64 + {CC7B1FE4-CF75-45EC-9B64-6AE500D81E8C}.Release|Any CPU.ActiveCfg = Release|x64 + {CC7B1FE4-CF75-45EC-9B64-6AE500D81E8C}.Release|Any CPU.Build.0 = Release|x64 {CC7B1FE4-CF75-45EC-9B64-6AE500D81E8C}.Release|x64.ActiveCfg = Release|x64 {CC7B1FE4-CF75-45EC-9B64-6AE500D81E8C}.Release|x64.Build.0 = Release|x64 + {994C74C7-3CC0-4A35-9A20-BDAB738FE24B}.Debug|Any CPU.ActiveCfg = Debug|x64 + {994C74C7-3CC0-4A35-9A20-BDAB738FE24B}.Debug|Any CPU.Build.0 = Debug|x64 {994C74C7-3CC0-4A35-9A20-BDAB738FE24B}.Debug|x64.ActiveCfg = Debug|x64 {994C74C7-3CC0-4A35-9A20-BDAB738FE24B}.Debug|x64.Build.0 = Debug|x64 + {994C74C7-3CC0-4A35-9A20-BDAB738FE24B}.Release|Any CPU.ActiveCfg = Release|x64 + {994C74C7-3CC0-4A35-9A20-BDAB738FE24B}.Release|Any CPU.Build.0 = Release|x64 {994C74C7-3CC0-4A35-9A20-BDAB738FE24B}.Release|x64.ActiveCfg = Release|x64 {994C74C7-3CC0-4A35-9A20-BDAB738FE24B}.Release|x64.Build.0 = Release|x64 + {1955598A-A37F-4B3F-953F-7AC8D8770858}.Debug|Any CPU.ActiveCfg = Debug|x64 + {1955598A-A37F-4B3F-953F-7AC8D8770858}.Debug|Any CPU.Build.0 = Debug|x64 {1955598A-A37F-4B3F-953F-7AC8D8770858}.Debug|x64.ActiveCfg = Debug|x64 {1955598A-A37F-4B3F-953F-7AC8D8770858}.Debug|x64.Build.0 = Debug|x64 + {1955598A-A37F-4B3F-953F-7AC8D8770858}.Release|Any CPU.ActiveCfg = Release|x64 + {1955598A-A37F-4B3F-953F-7AC8D8770858}.Release|Any CPU.Build.0 = Release|x64 {1955598A-A37F-4B3F-953F-7AC8D8770858}.Release|x64.ActiveCfg = Release|x64 {1955598A-A37F-4B3F-953F-7AC8D8770858}.Release|x64.Build.0 = Release|x64 + {77FA0E72-8690-4433-BE41-179F506E2E51}.Debug|Any CPU.ActiveCfg = Debug|x64 + {77FA0E72-8690-4433-BE41-179F506E2E51}.Debug|Any CPU.Build.0 = Debug|x64 + {77FA0E72-8690-4433-BE41-179F506E2E51}.Debug|x64.ActiveCfg = Debug|x64 + {77FA0E72-8690-4433-BE41-179F506E2E51}.Debug|x64.Build.0 = Debug|x64 + {77FA0E72-8690-4433-BE41-179F506E2E51}.Release|Any CPU.ActiveCfg = Release|x64 + {77FA0E72-8690-4433-BE41-179F506E2E51}.Release|Any CPU.Build.0 = Release|x64 + {77FA0E72-8690-4433-BE41-179F506E2E51}.Release|x64.ActiveCfg = Release|x64 + {77FA0E72-8690-4433-BE41-179F506E2E51}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -268,6 +412,7 @@ Global {CC7B1FE4-CF75-45EC-9B64-6AE500D81E8C} = {C3C023F8-74CB-4201-A926-C86D22DDB1CF} {994C74C7-3CC0-4A35-9A20-BDAB738FE24B} = {9F7BCE27-2A3F-4D82-8F52-903C9B356F25} {1955598A-A37F-4B3F-953F-7AC8D8770858} = {9F7BCE27-2A3F-4D82-8F52-903C9B356F25} + {77FA0E72-8690-4433-BE41-179F506E2E51} = {046D778E-C7B5-4086-A056-F0526C7C3A96} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {98BB8C58-726D-4AB8-A113-D44B55413B79} diff --git a/Totoro.WinUI/Totoro.WinUI.csproj b/Totoro.WinUI/Totoro.WinUI.csproj index 688f42eb..6239e248 100644 --- a/Totoro.WinUI/Totoro.WinUI.csproj +++ b/Totoro.WinUI/Totoro.WinUI.csproj @@ -63,6 +63,7 @@ + @@ -97,6 +98,9 @@ MSBuild:Compile + + $(DefaultXamlRuntime) + $(DefaultXamlRuntime) Designer @@ -255,6 +259,7 @@ + diff --git a/Totoro.WinUI/ViewModels/NowPlayingViewModel.cs b/Totoro.WinUI/ViewModels/NowPlayingViewModel.cs index 558d417f..fa43b6ef 100644 --- a/Totoro.WinUI/ViewModels/NowPlayingViewModel.cs +++ b/Totoro.WinUI/ViewModels/NowPlayingViewModel.cs @@ -83,23 +83,21 @@ public NowPlayingViewModel(IAnimeServiceContext animeServiceContext, [Reactive] public bool IsVisible { get; set; } [Reactive] public ObservableCollection> Info { get; set; } [Reactive] public bool IsPositionVisible { get; set; } - [Reactive] public EpisodeModelCollection EpisodeModels { get; set; } public int EpisodeInt { get; set; } public INativeMediaPlayer MediaPlayer { get; set; } public string ProviderType { get; set; } public AnimeProvider Provider { get; set; } - public override Task OnNavigatedTo(IReadOnlyDictionary parameters) + public override async Task OnNavigatedTo(IReadOnlyDictionary parameters) { if (parameters.ContainsKey("Player")) { IsVisible = true; MediaPlayer = (INativeMediaPlayer)parameters["Player"]; - InitializeFromPlayer(MediaPlayer); + await InitializeFromPlayer(MediaPlayer); } - return Task.CompletedTask; } public async Task SetAnime(long animeId) @@ -117,7 +115,7 @@ public async Task SetAnime(long animeId) ]; } - public async void InitializeFromPlayer(INativeMediaPlayer player) + public async Task InitializeFromPlayer(INativeMediaPlayer player) { IsPositionVisible = player is IHavePosition; @@ -135,7 +133,7 @@ public async void InitializeFromPlayer(INativeMediaPlayer player) .ToPropertyEx(this, x => x.Position, initialValue: TimeSpan.Zero); } - var fileName = player.GetTitle(); + var fileName = await player.TitleChanged.FirstOrDefaultAsync(x => !string.IsNullOrEmpty(x)); var parsedResults = AnitomySharp.AnitomySharp.Parse(fileName); var title = parsedResults.FirstOrDefault(x => x.Category == AnitomySharp.Element.ElementCategory.ElementAnimeTitle)?.Value; @@ -165,7 +163,7 @@ public async void InitializeFromPlayer(INativeMediaPlayer player) private Task GetTimeStamps(AnimeModel anime, TimeSpan duration) { return anime.MalId is { } malId - ? _timestampsService.GetTimeStampsWithMalId(malId, EpisodeModels.Current.EpisodeNumber, duration.TotalSeconds) - : _timestampsService.GetTimeStampsWithMalId(anime.Id, EpisodeModels.Current.EpisodeNumber, duration.TotalSeconds); + ? _timestampsService.GetTimeStampsWithMalId(malId, EpisodeInt, duration.TotalSeconds) + : _timestampsService.GetTimeStampsWithMalId(anime.Id, EpisodeInt, duration.TotalSeconds); } } diff --git a/plugins.json b/plugins.json index 553bad4e..b4045b26 100644 --- a/plugins.json +++ b/plugins.json @@ -48,18 +48,23 @@ "MediaDetection": [ { "DisplayName": "Vlc", - "Version": "1.4.0.0", + "Version": "1.5.0.0", "FileName": "Totoro.Plugins.MediaDetection.Vlc.dll" }, { "DisplayName": "Window 11 Media Player", - "Version": "1.3.0.0", + "Version": "1.4.0.0", "FileName": "Totoro.Plugins.MediaDetection.Win11MediaPlayer.dll" }, { "DisplayName": "General Purpose", - "Version": "1.4.0.0", + "Version": "1.5.0.0", "FileName": "Totoro.Plugins.MediaDetection.Generic.dll" + }, + { + "DisplayName": "MPC-HC", + "Version": "1.0.0.0", + "FileName": "Totoro.Plugins.MediaDetection.MpcHc.dll" } ] } \ No newline at end of file