DreamineVMS 1.0.0.0
Windows PC의 RTSP·USB 카메라 영상을 HLS로 변환해 원격 CCTV Viewer에 전달하는 데스크톱 에이전트입니다.
로딩중...
검색중...
일치하는것 없음
VmsBlazorServerHostedService.cs
이 파일의 문서화 페이지로 가기
1using Dreamine.Hybrid.Interfaces;
2using Dreamine.Hybrid.State;
12using Microsoft.AspNetCore.Builder;
13using Microsoft.AspNetCore.Components;
14using Microsoft.AspNetCore.Components.Server;
15using Microsoft.AspNetCore.Hosting;
16using Microsoft.AspNetCore.Hosting.StaticWebAssets;
17using Microsoft.AspNetCore.Http;
18using Microsoft.AspNetCore.StaticFiles;
19using Microsoft.Extensions.DependencyInjection;
20using Microsoft.Extensions.Hosting;
21using Microsoft.Extensions.Options;
22using System.IO;
23using System.Reflection;
24
26
43public sealed class VmsBlazorServerHostedService<TRootComponent> : IHostedService
44 where TRootComponent : IComponent
45{
54 private readonly IServiceProvider _rootServiceProvider;
63 private readonly VmsServerOptions _options;
72 private IHost? _webHost;
73
107 IServiceProvider rootServiceProvider,
108 IOptions<VmsServerOptions> options)
109 {
110 _rootServiceProvider = rootServiceProvider ?? throw new ArgumentNullException(nameof(rootServiceProvider));
111 _options = options?.Value ?? throw new ArgumentNullException(nameof(options));
112 }
113
138 public async Task StartAsync(CancellationToken cancellationToken)
139 {
140 Assembly blazorAssembly = typeof(TRootComponent).Assembly;
141
142 WebApplicationBuilder builder = WebApplication.CreateBuilder(new WebApplicationOptions
143 {
144 Args = Array.Empty<string>(),
145 ContentRootPath = AppContext.BaseDirectory,
146 ApplicationName = blazorAssembly.GetName().Name
147 });
148
149 StaticWebAssetsLoader.UseStaticWebAssets(builder.Environment, builder.Configuration);
150
151 builder.Services
152 .AddRazorComponents()
153 .AddInteractiveServerComponents();
154
155 builder.Services.Configure<CircuitOptions>(options =>
156 {
157 options.DetailedErrors = true;
158 });
159
160 RegisterSharedServices(builder.Services);
161 RegisterBlazorViewModels(builder.Services);
162
163 builder.WebHost.ConfigureKestrel(options =>
164 {
165 if (_options.ListenAnyIp)
166 {
167 options.ListenAnyIP(_options.Port);
168 }
169 else
170 {
171 options.ListenLocalhost(_options.Port);
172 }
173 });
174
175 WebApplication app = builder.Build();
176
177 FileExtensionContentTypeProvider contentTypes = CreateHlsContentTypeProvider();
178
180 UseHlsStaticFiles(app, contentTypes);
181
182 app.UseRouting();
183 app.UseAntiforgery();
184
185 app.MapGet("/healthz", () => Results.Text("OK", "text/plain"));
186
187 app.MapRazorComponents<TRootComponent>()
188 .AddInteractiveServerRenderMode();
189
190 _webHost = app;
191
192 await app.StartAsync(cancellationToken).ConfigureAwait(false);
193 }
194
219 public async Task StopAsync(CancellationToken cancellationToken)
220 {
221 if (_webHost is null)
222 {
223 return;
224 }
225
226 try
227 {
228 await _webHost.StopAsync(cancellationToken).ConfigureAwait(false);
229 }
230 finally
231 {
232 _webHost.Dispose();
233 _webHost = null;
234 }
235 }
236
253 private void RegisterSharedServices(IServiceCollection services)
254 {
255 AddShared<IHybridMessageBus>(services);
256 AddShared<IHybridStateStore<VmsDashboardState>>(services);
257 AddShared<IVmsCameraRepository>(services);
258 AddShared<ICameraRuntimeStateService>(services);
259 AddShared<ICameraStreamService>(services);
260 AddShared<IVmsDashboardStateService>(services);
261
262 }
263
280 private static void RegisterBlazorViewModels(IServiceCollection services)
281 {
282 services.AddScoped<LivePageViewModel>();
283 services.AddScoped<DashboardPageViewModel>();
284 services.AddScoped<VmsLocalDashboardViewModel>();
285 }
286
311 private void AddShared<TService>(IServiceCollection services)
312 where TService : class
313 {
314 TService service = _rootServiceProvider.GetRequiredService<TService>();
315 services.AddSingleton(service);
316 }
317
334 private static FileExtensionContentTypeProvider CreateHlsContentTypeProvider()
335 {
336 FileExtensionContentTypeProvider contentTypes = new();
337
338 contentTypes.Mappings[".m3u8"] = "application/vnd.apple.mpegurl";
339 contentTypes.Mappings[".ts"] = "video/mp2t";
340 contentTypes.Mappings[".mp4"] = "video/mp4";
341
342 return contentTypes;
343 }
344
361 private static void RemoveRangeHeadersForHls(WebApplication app)
362 {
363 app.Use(async (context, next) =>
364 {
365 PathString path = context.Request.Path;
366
367 if (IsHlsPath(path))
368 {
369 context.Request.Headers.Remove("Range");
370 context.Request.Headers.Remove("If-Range");
371 }
372
373 await next().ConfigureAwait(false);
374 });
375 }
376
401 private static void UseHlsStaticFiles(
402 WebApplication app,
403 FileExtensionContentTypeProvider contentTypes)
404 {
405 app.UseStaticFiles(new StaticFileOptions
406 {
407 ContentTypeProvider = contentTypes,
408 OnPrepareResponse = context =>
409 {
410 string extension = Path.GetExtension(context.File.PhysicalPath ?? string.Empty)
411 .ToLowerInvariant();
412
413 if (extension is ".m3u8" or ".ts")
414 {
415 IHeaderDictionary headers = context.Context.Response.Headers;
416
417 headers["Cache-Control"] = "no-store, must-revalidate";
418 headers["Pragma"] = "no-cache";
419 headers["Expires"] = "0";
420 headers["Accept-Ranges"] = "none";
421 }
422 }
423 });
424 }
425
450 private static bool IsHlsPath(PathString path)
451 {
452 if (!path.HasValue)
453 {
454 return false;
455 }
456
457 return path.Value.EndsWith(".m3u8", StringComparison.OrdinalIgnoreCase)
458 || path.Value.EndsWith(".ts", StringComparison.OrdinalIgnoreCase);
459 }
460}
static void UseHlsStaticFiles(WebApplication app, FileExtensionContentTypeProvider contentTypes)
async Task StopAsync(CancellationToken cancellationToken)
async Task StartAsync(CancellationToken cancellationToken)
static FileExtensionContentTypeProvider CreateHlsContentTypeProvider()
static void RegisterBlazorViewModels(IServiceCollection services)
VmsBlazorServerHostedService(IServiceProvider rootServiceProvider, IOptions< VmsServerOptions > options)