DreamineVMS
1.0.0.0
Windows PC의 RTSP·USB 카메라 영상을 HLS로 변환해 원격 CCTV Viewer에 전달하는 데스크톱 에이전트입니다.
Toggle main menu visibility
로딩중...
검색중...
일치하는것 없음
MainWindow.xaml.cs
이 파일의 문서화 페이지로 가기
1
using
Dreamine.Hybrid.Wpf.Controls;
2
using
Dreamine.Hybrid.Wpf.Hosting;
3
using
DreamineVMS.Blazor.Components;
4
using
DreamineVMS.Options
;
5
using
DreamineVMS.ViewModels
;
6
using
Microsoft.Extensions.Options;
7
using
Microsoft.Web.WebView2.Core;
8
using
Microsoft.Web.WebView2.Wpf;
9
using
System;
10
using
System.ComponentModel;
11
using
System.Diagnostics;
12
using
System.Net.Http;
13
using
System.Threading;
14
using
System.Threading.Tasks;
15
using
System.Windows;
16
17
namespace
DreamineVMS.Views
;
18
27
public
partial class
MainWindow
: Window
28
{
37
private
readonly
VmsServerOptions
_serverOptions
;
46
private
MainWindowViewModel
?
_attachedViewModel
;
55
private
WebView2?
_wpfLiveWebView
;
64
private
WebView2?
_camerasWebView
;
73
private
bool
_wpfLiveInitialized
;
82
private
bool
_camerasInitialized
;
83
108
public
MainWindow
(IOptions<VmsServerOptions> serverOptions)
109
{
110
_serverOptions
= serverOptions?.Value ??
throw
new
ArgumentNullException(nameof(serverOptions));
111
InitializeComponent();
112
113
if
(DesignerProperties.GetIsInDesignMode(
this
))
114
{
115
return
;
116
}
117
118
DataContextChanged +=
OnDataContextChanged
;
119
Loaded +=
OnLoaded
;
120
Closed +=
OnClosed
;
121
MainTabControl.SelectionChanged +=
OnMainTabSelectionChanged
;
122
}
123
148
private
void
OnDataContextChanged
(
object
sender, DependencyPropertyChangedEventArgs e)
149
{
150
if
(
_attachedViewModel
is not
null
)
151
{
152
_attachedViewModel.OpenLiveTabRequested -=
OnOpenLiveTabRequested
;
153
_attachedViewModel
=
null
;
154
}
155
156
if
(DataContext is
MainWindowViewModel
viewModel)
157
{
158
_attachedViewModel
= viewModel;
159
_attachedViewModel.OpenLiveTabRequested +=
OnOpenLiveTabRequested
;
160
}
161
}
162
187
private
void
OnOpenLiveTabRequested
(
object
? sender, EventArgs e)
188
{
189
// 레거시 메시지 호환용입니다. Blazor Server의 첫 화면은 Live View이며, 탭 전환은 수행하지 않습니다.
190
Dispatcher.Invoke(() =>
191
{
192
if
(!IsActive)
193
{
194
Activate();
195
}
196
});
197
}
198
223
private
async
void
OnLoaded
(
object
sender, RoutedEventArgs e)
224
{
225
HybridHostControl embeddedDashboard =
new
()
226
{
227
HostPage =
"wwwroot/index.html"
,
228
Services
=
App
.
ServiceProvider
,
229
RootComponentType = typeof(VmsLocalDashboard),
230
RootSelector =
"#app"
231
};
232
233
EmbeddedDashboardTab.Content = embeddedDashboard;
234
235
// Live WebView는 동시에 두 개를 바로 띄우지 않습니다.
236
// 두 Live 탭이 동시에 /live에 접속하면 Blazor Circuit과 hls.js player가 중복 생성되어
237
// StopAll → StartAll 이후 source/session 동기화가 꼬일 수 있습니다.
238
await
EnsureSelectedLiveWebViewAsync
().ConfigureAwait(
true
);
239
}
240
265
private
void
OnClosed
(
object
? sender, EventArgs e)
266
{
267
if
(
_attachedViewModel
is not
null
)
268
{
269
_attachedViewModel.OpenLiveTabRequested -=
OnOpenLiveTabRequested
;
270
_attachedViewModel
=
null
;
271
}
272
273
// WebView2 인스턴스를 명시적으로 정리합니다.
274
// Dispose하지 않으면 Chromium 서브프로세스가 살아남아 앱 종료를 막을 수 있습니다.
275
TryDisposeWebView
(ref
_wpfLiveWebView
);
276
TryDisposeWebView
(ref
_camerasWebView
);
277
278
// WPF Shutdown을 명시적으로 트리거합니다.
279
// host.RunDreamineWpfApp의 구현에 따라 자동 종료가 안 될 수 있으므로 안전망입니다.
280
Application.Current?.Shutdown();
281
}
282
307
private
async
void
OnMainTabSelectionChanged
(
object
sender, System.Windows.Controls.SelectionChangedEventArgs e)
308
{
309
if
(!IsLoaded)
return
;
310
311
// 에이전트 설정 탭이 처음 열릴 때 PasswordBox에 저장된 비밀번호를 채워줍니다.
312
// PasswordBox.Password는 DependencyProperty가 아니라 직접 바인딩이 불가능합니다.
313
if
(AgentPasswordBox is not
null
314
&& DataContext is
MainWindowViewModel
vm
315
&&
string
.IsNullOrEmpty(AgentPasswordBox.Password)
316
&& !
string
.IsNullOrEmpty(vm.AgentSettings.Password))
317
{
318
AgentPasswordBox.Password = vm.AgentSettings.Password;
319
}
320
321
await
EnsureSelectedLiveWebViewAsync
().ConfigureAwait(
true
);
322
}
323
348
private
void
AgentPasswordBox_PasswordChanged
(
object
sender, RoutedEventArgs e)
349
{
350
if
(DataContext is
MainWindowViewModel
vm)
351
vm.AgentSettings.Password = AgentPasswordBox.Password;
352
}
353
370
private
async Task
EnsureSelectedLiveWebViewAsync
()
371
{
372
if
(MainTabControl.SelectedItem == WpfLiveTab)
373
{
374
await
EnsureWpfLiveWebViewAsync
().ConfigureAwait(
true
);
375
return
;
376
}
377
378
if
(MainTabControl.SelectedItem == CamerasTab)
379
{
380
await
EnsureCamerasWebViewAsync
().ConfigureAwait(
true
);
381
}
382
}
383
400
private
async Task
EnsureWpfLiveWebViewAsync
()
401
{
402
if
(
_wpfLiveInitialized
)
403
{
404
return
;
405
}
406
407
WebView2 webView = HybridWebViewHost.CreateWebView();
408
WpfLiveTab.Content = webView;
409
_wpfLiveWebView
= webView;
410
411
string
liveUrl =
GetLiveUrl
();
412
RegisterWebViewRecovery
(webView, () => liveUrl);
413
await
NavigateServerAsync
(webView, liveUrl).ConfigureAwait(
true
);
414
_wpfLiveInitialized
=
true
;
415
}
416
433
private
async Task
EnsureCamerasWebViewAsync
()
434
{
435
if
(
_camerasInitialized
)
436
{
437
return
;
438
}
439
440
WebView2 webView = HybridWebViewHost.CreateWebView();
441
CamerasTab.Content = webView;
442
_camerasWebView
= webView;
443
444
string
camerasUrl = $
"http://localhost:{_serverOptions.Port}/cameras"
;
445
RegisterWebViewRecovery
(webView, () => camerasUrl);
446
await
NavigateServerAsync
(webView, camerasUrl).ConfigureAwait(
true
);
447
_camerasInitialized
=
true
;
448
}
449
466
private
string
GetLiveUrl
()
467
{
468
return
$
"http://localhost:{_serverOptions.Port}/live"
;
469
}
470
495
private
void
RegisterWebViewRecovery
(WebView2 webView, Func<string> urlFactory)
496
{
497
webView.CoreWebView2InitializationCompleted += (_, e) =>
498
{
499
if
(!e.IsSuccess || webView.CoreWebView2 is
null
)
500
{
501
Debug.WriteLine($
"[DreamineVMS] WebView2 initialization failed: {e.InitializationException}"
);
502
return
;
503
}
504
505
webView.CoreWebView2.ProcessFailed += async (_, args) =>
506
{
507
Debug.WriteLine($
"[DreamineVMS] WebView2 process failed: {args.ProcessFailedKind}. Reloading..."
);
508
509
await Dispatcher.InvokeAsync(async () =>
510
{
511
await Task.Delay(1000).ConfigureAwait(
true
);
512
TryNavigateWebView
(webView, urlFactory());
513
});
514
};
515
};
516
517
webView.NavigationCompleted += (_, e) =>
518
{
519
if
(e.IsSuccess)
520
{
521
return
;
522
}
523
524
Debug.WriteLine($
"[DreamineVMS] WebView2 navigation failed: {e.WebErrorStatus}. Reloading..."
);
525
Dispatcher.InvokeAsync(async () =>
526
{
527
await Task.Delay(1500).ConfigureAwait(
true
);
528
TryNavigateWebView
(webView, urlFactory());
529
});
530
};
531
}
532
557
private
static
void
TryNavigateWebView
(WebView2 webView,
string
url)
558
{
559
try
560
{
561
webView.Source =
new
Uri(url);
562
}
563
catch
(Exception ex)
564
{
565
Debug.WriteLine($
"[DreamineVMS] WebView2 recovery navigation failed: {ex}"
);
566
}
567
}
568
585
private
static
void
TryDisposeWebView
(ref WebView2? webView)
586
{
587
if
(webView is
null
)
588
{
589
return
;
590
}
591
592
try
593
{
594
webView.Dispose();
595
}
596
catch
(Exception ex)
597
{
598
Debug.WriteLine($
"[DreamineVMS] WebView2 dispose failed: {ex}"
);
599
}
600
601
webView =
null
;
602
}
603
652
private
static
async Task
NavigateServerAsync
(WebView2 webView,
string
url,
int
timeoutMs = 15000,
int
intervalMs = 500)
653
{
654
try
655
{
656
await Task.Delay(1500).ConfigureAwait(
true
);
657
658
using
CancellationTokenSource cts =
new
(timeoutMs);
659
using
HttpClient http =
new
();
660
bool
alive =
false
;
661
662
while
(!cts.IsCancellationRequested)
663
{
664
try
665
{
666
HttpResponseMessage response = await http.GetAsync(url, cts.Token).ConfigureAwait(
true
);
667
if
((
int
)response.StatusCode >= 200 && (
int
)response.StatusCode < 400)
668
{
669
alive =
true
;
670
break
;
671
}
672
}
673
catch
(HttpRequestException)
674
{
675
// Server may not be ready yet.
676
}
677
catch
(TaskCanceledException)
678
{
679
// Retry until overall timeout.
680
}
681
682
await Task.Delay(intervalMs, cts.Token).ConfigureAwait(
true
);
683
}
684
685
if
(!alive)
686
{
687
Debug.WriteLine($
"[DreamineVMS] Server dashboard timeout: {url}"
);
688
await HybridWebViewHost.ShowOfflineMessageAsync(webView, url).ConfigureAwait(
true
);
689
return
;
690
}
691
692
webView.Source =
new
Uri(url);
693
}
694
catch
(TaskCanceledException)
695
{
696
Debug.WriteLine(
"[DreamineVMS] Server navigation canceled."
);
697
}
698
catch
(Exception ex)
699
{
700
Debug.WriteLine($
"[DreamineVMS] Server navigation failed: {ex}"
);
701
}
702
}
703
}
DreamineVMS.Options
Definition
FfmpegOptions.cs:1
DreamineVMS.Services
Definition
AgentApiClient.cs:7
DreamineVMS.ViewModels
Definition
AgentSettingsViewModel.cs:7
DreamineVMS.Views
Definition
AgentStatusConverters.cs:5
DreamineVMS.App
Definition
App.xaml.cs:19
DreamineVMS.App.ServiceProvider
static ? IServiceProvider ServiceProvider
Definition
App.xaml.cs:28
DreamineVMS.Options.VmsServerOptions
Definition
VmsServerOptions.cs:12
DreamineVMS.ViewModels.MainWindowViewModel
Definition
MainWindowViewModel.cs:32
DreamineVMS.Views.MainWindow.TryNavigateWebView
static void TryNavigateWebView(WebView2 webView, string url)
Definition
MainWindow.xaml.cs:557
DreamineVMS.Views.MainWindow.OnDataContextChanged
void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
Definition
MainWindow.xaml.cs:148
DreamineVMS.Views.MainWindow.NavigateServerAsync
static async Task NavigateServerAsync(WebView2 webView, string url, int timeoutMs=15000, int intervalMs=500)
Definition
MainWindow.xaml.cs:652
DreamineVMS.Views.MainWindow._serverOptions
readonly VmsServerOptions _serverOptions
Definition
MainWindow.xaml.cs:37
DreamineVMS.Views.MainWindow.OnOpenLiveTabRequested
void OnOpenLiveTabRequested(object? sender, EventArgs e)
Definition
MainWindow.xaml.cs:187
DreamineVMS.Views.MainWindow.TryDisposeWebView
static void TryDisposeWebView(ref WebView2? webView)
Definition
MainWindow.xaml.cs:585
DreamineVMS.Views.MainWindow._camerasWebView
WebView2? _camerasWebView
Definition
MainWindow.xaml.cs:64
DreamineVMS.Views.MainWindow._camerasInitialized
bool _camerasInitialized
Definition
MainWindow.xaml.cs:82
DreamineVMS.Views.MainWindow.MainWindow
MainWindow(IOptions< VmsServerOptions > serverOptions)
Definition
MainWindow.xaml.cs:108
DreamineVMS.Views.MainWindow._wpfLiveInitialized
bool _wpfLiveInitialized
Definition
MainWindow.xaml.cs:73
DreamineVMS.Views.MainWindow.OnMainTabSelectionChanged
async void OnMainTabSelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
Definition
MainWindow.xaml.cs:307
DreamineVMS.Views.MainWindow.RegisterWebViewRecovery
void RegisterWebViewRecovery(WebView2 webView, Func< string > urlFactory)
Definition
MainWindow.xaml.cs:495
DreamineVMS.Views.MainWindow.EnsureCamerasWebViewAsync
async Task EnsureCamerasWebViewAsync()
Definition
MainWindow.xaml.cs:433
DreamineVMS.Views.MainWindow.EnsureSelectedLiveWebViewAsync
async Task EnsureSelectedLiveWebViewAsync()
Definition
MainWindow.xaml.cs:370
DreamineVMS.Views.MainWindow.GetLiveUrl
string GetLiveUrl()
Definition
MainWindow.xaml.cs:466
DreamineVMS.Views.MainWindow._attachedViewModel
MainWindowViewModel? _attachedViewModel
Definition
MainWindow.xaml.cs:46
DreamineVMS.Views.MainWindow._wpfLiveWebView
WebView2? _wpfLiveWebView
Definition
MainWindow.xaml.cs:55
DreamineVMS.Views.MainWindow.AgentPasswordBox_PasswordChanged
void AgentPasswordBox_PasswordChanged(object sender, RoutedEventArgs e)
Definition
MainWindow.xaml.cs:348
DreamineVMS.Views.MainWindow.EnsureWpfLiveWebViewAsync
async Task EnsureWpfLiveWebViewAsync()
Definition
MainWindow.xaml.cs:400
DreamineVMS.Views.MainWindow.OnLoaded
async void OnLoaded(object sender, RoutedEventArgs e)
Definition
MainWindow.xaml.cs:223
DreamineVMS.Views.MainWindow.OnClosed
void OnClosed(object? sender, EventArgs e)
Definition
MainWindow.xaml.cs:265
Views
MainWindow.xaml.cs
다음에 의해 생성됨 :
1.17.0