Harden hidden startup and sleep recovery
This commit is contained in:
1 parent
4088f2299d
commit
2819d31490
3 files changed
+205
-13
No files matched your search
@@ -31,10 +31,13 @@ RTSP는 `ddagrab` 기반 Desktop Duplication API로 화면과 마우스 커서
|
|||||||
- 기존 MediaMTX가 RTSP 포트를 사용 중이면 해당 프로세스 재사용
|
- 기존 MediaMTX가 RTSP 포트를 사용 중이면 해당 프로세스 재사용
|
||||||
- 자동 생성 MediaMTX 설정은 RTSP over TCP만 사용해 UDP RTP 포트 충돌 방지
|
- 자동 생성 MediaMTX 설정은 RTSP over TCP만 사용해 UDP RTP 포트 충돌 방지
|
||||||
- 로그인 시 자동 실행 작업 등록 및 제거
|
- 로그인 시 자동 실행 작업 등록 및 제거
|
||||||
|
- 기본 실행과 예약 작업 모두 PowerShell 창을 표시하지 않는 숨김 백그라운드 실행
|
||||||
|
- 절전 해제나 장시간 polling 중단 감지 시 전체 채널 강제 재구성
|
||||||
|
- 로그인 트리거와 1분 간격 무기한 복구 트리거를 이용한 Watchdog 지속 복구
|
||||||
- `config.ini` 기반 기능별 설정과 명령줄 override
|
- `config.ini` 기반 기능별 설정과 명령줄 override
|
||||||
- 오디오 캡처/AAC 트랙을 `1` 또는 `0`으로 활성화·비활성화
|
- 오디오 캡처/AAC 트랙을 `1` 또는 `0`으로 활성화·비활성화
|
||||||
- FFmpeg와 MediaMTX 부재 시 HTTPS 다운로드, SHA-256 검증, 자동 설치
|
- FFmpeg와 MediaMTX 부재 시 HTTPS 다운로드, SHA-256 검증, 자동 설치
|
||||||
- 모니터·오디오·NIC·프로세스·설정·안전장치용 내장 시뮬레이션 30건
|
- 모니터·오디오·NIC·프로세스·절전 복구·설정·안전장치용 내장 시뮬레이션 35건
|
||||||
|
|
||||||
## RTSP URL
|
## RTSP URL
|
||||||
|
|
||||||
@@ -132,7 +135,7 @@ INI를 해석한 최종 설정값만 확인하고 종료할 수도 있습니다.
|
|||||||
|
|
||||||
## 내장 시뮬레이션
|
## 내장 시뮬레이션
|
||||||
|
|
||||||
실제 송출 프로세스나 예약 작업을 만들지 않고 주요 판단 로직 30건을 검사합니다.
|
실제 송출 프로세스나 예약 작업을 만들지 않고 주요 판단 로직 35건을 검사합니다.
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
.\screen-rtsp.ps1 -RunSimulationTests
|
.\screen-rtsp.ps1 -RunSimulationTests
|
||||||
@@ -153,20 +156,38 @@ INI를 해석한 최종 설정값만 확인하고 종료할 수도 있습니다.
|
|||||||
|
|
||||||
## 설치 및 실행
|
## 설치 및 실행
|
||||||
|
|
||||||
관리자 PowerShell에서 실행 정책을 우회해 시작합니다.
|
관리자 PowerShell에서 실행 정책을 우회해 시작합니다. 기본 실행은 최종 설정값과 명령줄 override를 숨김 worker에 전달한 뒤 현재 PowerShell 프롬프트로 즉시 돌아옵니다. 현재 창을 닫아도 송출은 계속됩니다.
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
Set-Location C:\RTSP
|
Set-Location C:\RTSP
|
||||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\screen-rtsp.ps1
|
powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\screen-rtsp.ps1
|
||||||
```
|
```
|
||||||
|
|
||||||
|
진단 목적으로 현재 콘솔에서 직접 로그를 확인할 때만 `-Foreground`를 사용합니다. 이 경우 창을 닫거나 `Ctrl+C`를 누르면 송출도 종료됩니다.
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\screen-rtsp.ps1 -Foreground
|
||||||
|
```
|
||||||
|
|
||||||
로그인 자동 실행은 대화형 사용자 세션에 예약 작업을 등록합니다. Desktop Duplication과 사용자 재생 오디오를 캡처해야 하므로 Windows 서비스가 아니라 로그인 트리거를 사용합니다.
|
로그인 자동 실행은 대화형 사용자 세션에 예약 작업을 등록합니다. Desktop Duplication과 사용자 재생 오디오를 캡처해야 하므로 Windows 서비스가 아니라 로그인 트리거를 사용합니다.
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\screen-rtsp.ps1 -InstallStartup
|
powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\screen-rtsp.ps1 -InstallStartup
|
||||||
|
Start-ScheduledTask -TaskName "Screen RTSP Watchdog"
|
||||||
```
|
```
|
||||||
|
|
||||||
등록된 작업은 `config.ini` 경로만 전달하므로 설정 파일을 수정한 뒤 작업을 다시 시작하면 변경값이 적용됩니다.
|
등록된 작업은 다음 복구 정책을 사용합니다.
|
||||||
|
|
||||||
|
- PowerShell 작업 인자와 프로세스 창 스타일을 모두 `Hidden`으로 지정
|
||||||
|
- 로그인 즉시 시작
|
||||||
|
- 1분마다 작업 생존을 무기한 확인하고, 실행 중이면 `IgnoreNew`로 중복 실행 방지
|
||||||
|
- 작업 실패 시 1분 간격으로 최대 999회 재시작하고, 횟수가 소진돼도 무기한 반복 트리거가 계속 복구
|
||||||
|
- 절전 중 놓친 실행은 `StartWhenAvailable`로 절전 해제 후 시작
|
||||||
|
- 배터리 전환 시에도 중지하지 않으며 실행 시간 제한 없음
|
||||||
|
|
||||||
|
Watchdog 내부에서는 Windows `PowerModeChanged/Resume` 이벤트를 감시하고, 이벤트 누락에 대비해 polling 시간 공백도 함께 확인합니다. 절전 해제가 감지되면 FFmpeg 2채널을 모두 종료하고 모니터·NIC·오디오·GPU 구성을 다시 읽은 뒤 안정화 시간 이후 재시작합니다. 모니터 분리 시에는 채널을 종료하고 계속 대기하며, 필요한 수만큼 다시 연결되면 동일하게 자동 복구합니다.
|
||||||
|
|
||||||
|
등록된 작업은 `config.ini` 경로만 전달하므로 설정 파일을 수정한 뒤 작업을 다시 시작하면 변경값이 적용됩니다. 기존 작업이 등록돼 있다면 업데이트된 복구 설정을 적용하기 위해 `-InstallStartup`을 다시 실행해야 합니다.
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\screen-rtsp.ps1 -StartupStatus
|
powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\screen-rtsp.ps1 -StartupStatus
|
||||||
@@ -174,6 +195,10 @@ Stop-ScheduledTask -TaskName "Screen RTSP Watchdog"
|
|||||||
Start-ScheduledTask -TaskName "Screen RTSP Watchdog"
|
Start-ScheduledTask -TaskName "Screen RTSP Watchdog"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`Stop-ScheduledTask`만 실행하면 무기한 반복 트리거가 1분 이내에 다시 시작할 수 있습니다. 완전히 중지하려면 아래의 `-UninstallStartup`을 사용합니다.
|
||||||
|
|
||||||
|
무기한 복구는 사용자가 로그인한 대화형 세션에서 예약 작업이 등록·활성화된 동안 적용됩니다. 작업 자체를 삭제하거나 비활성화한 경우와 전원이 꺼진 동안에는 실행할 수 없으며, 다음 로그인 때 다시 시작합니다.
|
||||||
|
|
||||||
자동 실행 제거:
|
자동 실행 제거:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
|
|||||||
@@ -13,7 +13,10 @@
|
|||||||
// 슬래시 두 개로 시작하는 이 줄도 설명으로 처리됩니다.
|
// 슬래시 두 개로 시작하는 이 줄도 설명으로 처리됩니다.
|
||||||
#
|
#
|
||||||
# 실행
|
# 실행
|
||||||
|
# 기본 실행은 즉시 숨김 백그라운드 worker를 시작하고 현재 창으로 돌아옵니다.
|
||||||
# powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\screen-rtsp.ps1
|
# powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\screen-rtsp.ps1
|
||||||
|
# 진단 중 콘솔 로그를 직접 보려는 경우에만 -Foreground를 사용합니다.
|
||||||
|
# powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\screen-rtsp.ps1 -Foreground
|
||||||
# powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\screen-rtsp.ps1 -ShowConfiguration
|
# powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\screen-rtsp.ps1 -ShowConfiguration
|
||||||
# powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\screen-rtsp.ps1 -InstallDependenciesOnly
|
# powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\screen-rtsp.ps1 -InstallDependenciesOnly
|
||||||
# powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\screen-rtsp.ps1 -RunSimulationTests
|
# powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\screen-rtsp.ps1 -RunSimulationTests
|
||||||
@@ -25,6 +28,7 @@
|
|||||||
# powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\screen-rtsp.ps1 -InstallStartup
|
# powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\screen-rtsp.ps1 -InstallStartup
|
||||||
# powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\screen-rtsp.ps1 -StartupStatus
|
# powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\screen-rtsp.ps1 -StartupStatus
|
||||||
# Start-ScheduledTask -TaskName "Screen RTSP Watchdog"
|
# Start-ScheduledTask -TaskName "Screen RTSP Watchdog"
|
||||||
|
# Stop-ScheduledTask는 1분 이내 자동 복구되므로 완전 중지는 -UninstallStartup을 사용합니다.
|
||||||
# powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\screen-rtsp.ps1 -UninstallStartup
|
# powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\screen-rtsp.ps1 -UninstallStartup
|
||||||
#
|
#
|
||||||
# GPU와 FFmpeg 지원 확인
|
# GPU와 FFmpeg 지원 확인
|
||||||
|
|||||||
+172
-9
@@ -54,6 +54,10 @@ param(
|
|||||||
[switch]$ShowConfiguration,
|
[switch]$ShowConfiguration,
|
||||||
[switch]$InstallDependenciesOnly,
|
[switch]$InstallDependenciesOnly,
|
||||||
[switch]$RunSimulationTests,
|
[switch]$RunSimulationTests,
|
||||||
|
[switch]$Foreground,
|
||||||
|
|
||||||
|
[Parameter(DontShow = $true)]
|
||||||
|
[switch]$WatchdogWorker,
|
||||||
|
|
||||||
[Parameter(DontShow = $true)]
|
[Parameter(DontShow = $true)]
|
||||||
[switch]$AudioWorker,
|
[switch]$AudioWorker,
|
||||||
@@ -833,24 +837,74 @@ function Join-ProcessArguments {
|
|||||||
|
|
||||||
function Get-StartupArgumentList {
|
function Get-StartupArgumentList {
|
||||||
$values = @(
|
$values = @(
|
||||||
"-NoProfile", "-ExecutionPolicy", "Bypass", "-File", $PSCommandPath,
|
"-NoProfile", "-WindowStyle", "Hidden", "-ExecutionPolicy", "Bypass",
|
||||||
"-ConfigPath", $ConfigPath
|
"-File", $PSCommandPath, "-ConfigPath", $ConfigPath, "-WatchdogWorker"
|
||||||
)
|
)
|
||||||
return Join-ProcessArguments $values
|
return Join-ProcessArguments $values
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Get-HiddenWorkerArgumentList {
|
||||||
|
$values = @(
|
||||||
|
"-NoProfile", "-WindowStyle", "Hidden", "-ExecutionPolicy", "Bypass",
|
||||||
|
"-File", $PSCommandPath, "-ConfigPath", $ConfigPath, "-WatchdogWorker",
|
||||||
|
"-EncoderPriorityCsv", ($EncoderPriority -join ","),
|
||||||
|
"-ChannelCount", [string]$ChannelCount,
|
||||||
|
"-FrameRate", [string]$FrameRate,
|
||||||
|
"-VideoBitrate", $VideoBitrate,
|
||||||
|
"-AudioBitrate", $AudioBitrate,
|
||||||
|
"-AudioEnabled", [string]$AudioEnabled,
|
||||||
|
"-AudioSignalThreshold", $AudioSignalThreshold.ToString(
|
||||||
|
[System.Globalization.CultureInfo]::InvariantCulture
|
||||||
|
),
|
||||||
|
"-NicDescription", $NicDescription,
|
||||||
|
"-HealthTarget", $HealthTarget,
|
||||||
|
"-StableTimeSec", [string]$StableTimeSec,
|
||||||
|
"-PollIntervalSec", [string]$PollIntervalSec,
|
||||||
|
"-RtspPort", [string]$RtspPort,
|
||||||
|
"-AutoInstallDependencies", [string]$AutoInstallDependencies,
|
||||||
|
"-FFmpegDownloadUrl", $FFmpegDownloadUrl,
|
||||||
|
"-FFmpegSha256Url", $FFmpegSha256Url,
|
||||||
|
"-MediaMTXDownloadUrl", $MediaMTXDownloadUrl,
|
||||||
|
"-MediaMTXSha256", $MediaMTXSha256
|
||||||
|
)
|
||||||
|
return Join-ProcessArguments $values
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-StartupTriggers {
|
||||||
|
$logonTrigger = New-ScheduledTaskTrigger -AtLogOn `
|
||||||
|
-User "$env:USERDOMAIN\$env:USERNAME"
|
||||||
|
$recoveryTrigger = New-ScheduledTaskTrigger -Once -At ((Get-Date).AddMinutes(1)) `
|
||||||
|
-RepetitionInterval (New-TimeSpan -Minutes 1)
|
||||||
|
return @($logonTrigger, $recoveryTrigger)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-StartupSettings {
|
||||||
|
return New-ScheduledTaskSettingsSet -Hidden -StartWhenAvailable `
|
||||||
|
-AllowStartIfOnBatteries -DontStopIfGoingOnBatteries `
|
||||||
|
-RestartCount 999 -RestartInterval (New-TimeSpan -Minutes 1) `
|
||||||
|
-ExecutionTimeLimit ([TimeSpan]::Zero) -MultipleInstances IgnoreNew
|
||||||
|
}
|
||||||
|
|
||||||
|
function Start-HiddenWatchdog {
|
||||||
|
$process = Start-Process -FilePath $PowerShellPath `
|
||||||
|
-ArgumentList (Get-HiddenWorkerArgumentList) `
|
||||||
|
-WorkingDirectory $BaseDir -WindowStyle Hidden -PassThru
|
||||||
|
Write-Host "숨김 백그라운드 Watchdog를 시작했습니다. PID=$($process.Id)"
|
||||||
|
Write-Host "로그: $WatchdogLog"
|
||||||
|
}
|
||||||
|
|
||||||
function Install-StartupTask {
|
function Install-StartupTask {
|
||||||
$action = New-ScheduledTaskAction -Execute $PowerShellPath -Argument (Get-StartupArgumentList) `
|
$action = New-ScheduledTaskAction -Execute $PowerShellPath -Argument (Get-StartupArgumentList) `
|
||||||
-WorkingDirectory $BaseDir
|
-WorkingDirectory $BaseDir
|
||||||
$trigger = New-ScheduledTaskTrigger -AtLogOn -User "$env:USERDOMAIN\$env:USERNAME"
|
$triggers = @(Get-StartupTriggers)
|
||||||
$principal = New-ScheduledTaskPrincipal -UserId "$env:USERDOMAIN\$env:USERNAME" `
|
$principal = New-ScheduledTaskPrincipal -UserId "$env:USERDOMAIN\$env:USERNAME" `
|
||||||
-LogonType Interactive -RunLevel Highest
|
-LogonType Interactive -RunLevel Highest
|
||||||
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries `
|
$settings = Get-StartupSettings
|
||||||
-DontStopIfGoingOnBatteries -RestartCount 10 -RestartInterval (New-TimeSpan -Minutes 1) `
|
Register-ScheduledTask -TaskName $TaskName -Action $action -Trigger $triggers `
|
||||||
-ExecutionTimeLimit ([TimeSpan]::Zero) -MultipleInstances IgnoreNew
|
|
||||||
Register-ScheduledTask -TaskName $TaskName -Action $action -Trigger $trigger `
|
|
||||||
-Principal $principal -Settings $settings -Force | Out-Null
|
-Principal $principal -Settings $settings -Force | Out-Null
|
||||||
Write-Host "자동 실행 작업을 등록했습니다: $TaskName"
|
Write-Host "자동 실행 작업을 등록했습니다: $TaskName"
|
||||||
|
Write-Host "숨김 실행: 사용"
|
||||||
|
Write-Host "복구: 로그인 시 시작 + 1분 간격 무기한 확인 + 실패 시 재시작"
|
||||||
Write-Host "설정 파일: $ConfigPath"
|
Write-Host "설정 파일: $ConfigPath"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -905,6 +959,15 @@ if ($StartupStatus) {
|
|||||||
NextRunTime = $taskInfo.NextRunTime
|
NextRunTime = $taskInfo.NextRunTime
|
||||||
Execute = $task.Actions.Execute
|
Execute = $task.Actions.Execute
|
||||||
Arguments = $task.Actions.Arguments
|
Arguments = $task.Actions.Arguments
|
||||||
|
Hidden = $task.Settings.Hidden
|
||||||
|
StartWhenAvailable = $task.Settings.StartWhenAvailable
|
||||||
|
RestartCount = $task.Settings.RestartCount
|
||||||
|
RestartInterval = $task.Settings.RestartInterval
|
||||||
|
MultipleInstances = $task.Settings.MultipleInstances
|
||||||
|
TriggerCount = @($task.Triggers).Count
|
||||||
|
RecoveryInterval = (@($task.Triggers | Where-Object {
|
||||||
|
$_.Repetition -and $_.Repetition.Interval
|
||||||
|
} | ForEach-Object { $_.Repetition.Interval }) -join ", ")
|
||||||
} | Format-List
|
} | Format-List
|
||||||
exit 0
|
exit 0
|
||||||
}
|
}
|
||||||
@@ -1759,6 +1822,29 @@ function Get-StreamingReadinessReason {
|
|||||||
return $null
|
return $null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Test-WatchdogResumeGap {
|
||||||
|
param(
|
||||||
|
[DateTime]$PreviousPollAt,
|
||||||
|
[DateTime]$CurrentPollAt
|
||||||
|
)
|
||||||
|
$resumeThresholdSec = [Math]::Max(10.0, [double]$PollIntervalSec * 3.0)
|
||||||
|
return (($CurrentPollAt - $PreviousPollAt).TotalSeconds -ge $resumeThresholdSec)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Receive-WatchdogPowerResume {
|
||||||
|
param([string]$SourceIdentifier)
|
||||||
|
|
||||||
|
$resumeDetected = $false
|
||||||
|
foreach ($powerEvent in @(Get-Event -SourceIdentifier $SourceIdentifier `
|
||||||
|
-ErrorAction SilentlyContinue)) {
|
||||||
|
if ([string]$powerEvent.SourceEventArgs.Mode -eq "Resume") {
|
||||||
|
$resumeDetected = $true
|
||||||
|
}
|
||||||
|
Remove-Event -EventIdentifier $powerEvent.EventIdentifier -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
return $resumeDetected
|
||||||
|
}
|
||||||
|
|
||||||
function Invoke-SimulationTests {
|
function Invoke-SimulationTests {
|
||||||
$results = New-Object 'System.Collections.Generic.List[object]'
|
$results = New-Object 'System.Collections.Generic.List[object]'
|
||||||
|
|
||||||
@@ -1987,9 +2073,50 @@ function Invoke-SimulationTests {
|
|||||||
(Get-EncoderDefinition "Intel").Name -eq "h264_qsv")
|
(Get-EncoderDefinition "Intel").Name -eq "h264_qsv")
|
||||||
}
|
}
|
||||||
|
|
||||||
Invoke-SimulationCase "자동 실행은 config.ini 경로만 전달" {
|
Invoke-SimulationCase "자동 실행은 숨김 worker와 config.ini만 전달" {
|
||||||
$arguments = Get-StartupArgumentList
|
$arguments = Get-StartupArgumentList
|
||||||
return ($arguments -match '-ConfigPath' -and $arguments -notmatch '-VideoBitrate')
|
return ($arguments -match '-WindowStyle\s+Hidden' -and
|
||||||
|
$arguments -match '-WatchdogWorker' -and
|
||||||
|
$arguments -match '-ConfigPath' -and
|
||||||
|
$arguments -notmatch '-VideoBitrate')
|
||||||
|
}
|
||||||
|
|
||||||
|
Invoke-SimulationCase "직접 숨김 실행은 최종 설정과 override를 전달" {
|
||||||
|
$arguments = Get-HiddenWorkerArgumentList
|
||||||
|
return ($arguments -match '-EncoderPriorityCsv' -and
|
||||||
|
$arguments -match '-ChannelCount\s+2' -and
|
||||||
|
$arguments -match '-AudioEnabled\s+[01]' -and
|
||||||
|
$arguments -match '-RtspPort\s+8554' -and
|
||||||
|
$arguments -match '-WatchdogWorker')
|
||||||
|
}
|
||||||
|
|
||||||
|
Invoke-SimulationCase "예약 작업은 1분 간격으로 무기한 복구 확인" {
|
||||||
|
$triggers = @(Get-StartupTriggers)
|
||||||
|
$recoveryTrigger = $triggers | Where-Object {
|
||||||
|
$_.Repetition -and $_.Repetition.Interval
|
||||||
|
} | Select-Object -First 1
|
||||||
|
return ($triggers.Count -eq 2 -and
|
||||||
|
$recoveryTrigger.Repetition.Interval -eq "PT1M" -and
|
||||||
|
[string]::IsNullOrWhiteSpace($recoveryTrigger.Repetition.Duration))
|
||||||
|
}
|
||||||
|
|
||||||
|
Invoke-SimulationCase "예약 작업 숨김/누락 실행/재시작 설정" {
|
||||||
|
$settings = Get-StartupSettings
|
||||||
|
return ($settings.Hidden -and $settings.StartWhenAvailable -and
|
||||||
|
$settings.RestartCount -eq 999 -and
|
||||||
|
$settings.RestartInterval -eq "PT1M" -and
|
||||||
|
$settings.ExecutionTimeLimit -eq "PT0S" -and
|
||||||
|
$settings.MultipleInstances -eq "IgnoreNew")
|
||||||
|
}
|
||||||
|
|
||||||
|
Invoke-SimulationCase "정상 polling 간격은 절전 복구로 오인하지 않음" {
|
||||||
|
$previous = [DateTime]::UtcNow
|
||||||
|
return (-not (Test-WatchdogResumeGap $previous $previous.AddSeconds(2)))
|
||||||
|
}
|
||||||
|
|
||||||
|
Invoke-SimulationCase "긴 polling 공백을 절전 해제로 감지" {
|
||||||
|
$previous = [DateTime]::UtcNow
|
||||||
|
return (Test-WatchdogResumeGap $previous $previous.AddMinutes(5))
|
||||||
}
|
}
|
||||||
|
|
||||||
Invoke-SimulationCase "2채널 FFmpeg/오디오 worker가 모두 생존" {
|
Invoke-SimulationCase "2채널 FFmpeg/오디오 worker가 모두 생존" {
|
||||||
@@ -2066,13 +2193,24 @@ if ($RunSimulationTests) {
|
|||||||
exit 0
|
exit 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (-not $WatchdogWorker -and -not $Foreground) {
|
||||||
|
Start-HiddenWatchdog
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
$mutex = New-Object System.Threading.Mutex($false, "Local\ScreenRtspWatchdog")
|
$mutex = New-Object System.Threading.Mutex($false, "Local\ScreenRtspWatchdog")
|
||||||
$hasMutex = $false
|
$hasMutex = $false
|
||||||
|
$powerEventSourceIdentifier = "ScreenRtspPowerMode-$PID"
|
||||||
|
$powerEventRegistered = $false
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$hasMutex = $mutex.WaitOne(0, $false)
|
$hasMutex = $mutex.WaitOne(0, $false)
|
||||||
if (-not $hasMutex) { throw "Screen RTSP Watchdog가 이미 실행 중입니다." }
|
if (-not $hasMutex) { throw "Screen RTSP Watchdog가 이미 실행 중입니다." }
|
||||||
|
|
||||||
|
Register-ObjectEvent -InputObject ([Microsoft.Win32.SystemEvents]) `
|
||||||
|
-EventName PowerModeChanged -SourceIdentifier $powerEventSourceIdentifier | Out-Null
|
||||||
|
$powerEventRegistered = $true
|
||||||
|
|
||||||
Assert-RequiredFiles
|
Assert-RequiredFiles
|
||||||
Ensure-MediaMTXConfig
|
Ensure-MediaMTXConfig
|
||||||
$encoderList = Test-FFmpegCapabilities
|
$encoderList = Test-FFmpegCapabilities
|
||||||
@@ -2092,9 +2230,28 @@ try {
|
|||||||
$healthySince = $null
|
$healthySince = $null
|
||||||
$lastReason = ""
|
$lastReason = ""
|
||||||
$lastGpuCheck = [DateTime]::MinValue
|
$lastGpuCheck = [DateTime]::MinValue
|
||||||
|
$lastPollAt = Get-Date
|
||||||
|
|
||||||
while ($true) {
|
while ($true) {
|
||||||
try {
|
try {
|
||||||
|
$currentPollAt = Get-Date
|
||||||
|
$powerResumeDetected = Receive-WatchdogPowerResume `
|
||||||
|
-SourceIdentifier $powerEventSourceIdentifier
|
||||||
|
$resumeGapDetected = Test-WatchdogResumeGap `
|
||||||
|
-PreviousPollAt $lastPollAt -CurrentPollAt $currentPollAt
|
||||||
|
if ($powerResumeDetected -or $resumeGapDetected) {
|
||||||
|
$pollGapSec = [Math]::Round(($currentPollAt - $lastPollAt).TotalSeconds, 1)
|
||||||
|
$resumeSource = if ($powerResumeDetected) { "Windows 절전 해제 이벤트" } `
|
||||||
|
else { "polling 공백 ${pollGapSec}초" }
|
||||||
|
Write-Log "$resumeSource 감지 - 전체 채널 재구성"
|
||||||
|
Stop-Streams
|
||||||
|
$lastSystemSignature = ""
|
||||||
|
$healthySince = $null
|
||||||
|
$lastReason = ""
|
||||||
|
$lastGpuCheck = [DateTime]::MinValue
|
||||||
|
}
|
||||||
|
$lastPollAt = $currentPollAt
|
||||||
|
|
||||||
$mediaReady = Start-MediaMTX
|
$mediaReady = Start-MediaMTX
|
||||||
if (-not $mediaReady) {
|
if (-not $mediaReady) {
|
||||||
Stop-Streams
|
Stop-Streams
|
||||||
@@ -2189,6 +2346,12 @@ catch {
|
|||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
Stop-Streams
|
Stop-Streams
|
||||||
|
if ($powerEventRegistered) {
|
||||||
|
Unregister-Event -SourceIdentifier $powerEventSourceIdentifier `
|
||||||
|
-ErrorAction SilentlyContinue
|
||||||
|
Get-Event -SourceIdentifier $powerEventSourceIdentifier -ErrorAction SilentlyContinue |
|
||||||
|
Remove-Event -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
if ($OwnsMediaMTX -and (Test-ProcessRunning $MediaMTXProcess)) {
|
if ($OwnsMediaMTX -and (Test-ProcessRunning $MediaMTXProcess)) {
|
||||||
Stop-Process -Id $MediaMTXProcess.Id -Force -ErrorAction SilentlyContinue
|
Stop-Process -Id $MediaMTXProcess.Id -Force -ErrorAction SilentlyContinue
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user