Implement autostart functionality, ZIP packaging, and related installation scripts; update documentation and tests
This commit is contained in:
@@ -11,6 +11,7 @@ internal sealed class MdnsPublisher(string fingerprint, int port) : IDisposable
|
||||
private ServiceProfile? profile;
|
||||
private string addressesKey = "";
|
||||
private DateTimeOffset nextAnnouncement;
|
||||
private DateTimeOffset lastRefresh;
|
||||
public string Status { get; private set; } = "mDNS: ожидание локальной сети.";
|
||||
|
||||
public static ServiceProfile CreateProfile(string fingerprint, int port, IPAddress[] addresses)
|
||||
@@ -28,6 +29,10 @@ internal sealed class MdnsPublisher(string fingerprint, int port) : IDisposable
|
||||
{
|
||||
try
|
||||
{
|
||||
// Rebind after a long pause (sleep/resume), even if Windows kept the same IP.
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
if (lastRefresh != default && (now - lastRefresh > TimeSpan.FromSeconds(20) || now < lastRefresh)) Stop();
|
||||
lastRefresh = now;
|
||||
var key = string.Join(",", addresses.Select(a => a.ToString()));
|
||||
if (discovery is null || key != addressesKey)
|
||||
{
|
||||
|
||||
+19
-4
@@ -60,7 +60,10 @@ internal sealed class RemoteServer : IAsyncDisposable
|
||||
});
|
||||
app = builder.Build();
|
||||
app.UseRateLimiter();
|
||||
app.UseWebSockets(new WebSocketOptions { KeepAliveInterval = TimeSpan.FromSeconds(20) });
|
||||
app.UseWebSockets(new WebSocketOptions
|
||||
{
|
||||
KeepAliveInterval = TimeSpan.FromSeconds(20), KeepAliveTimeout = TimeSpan.FromSeconds(10)
|
||||
});
|
||||
app.Use(async (context, next) =>
|
||||
{
|
||||
context.Response.Headers.CacheControl = "no-store";
|
||||
@@ -116,7 +119,7 @@ internal sealed class RemoteServer : IAsyncDisposable
|
||||
public async Task StartAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
media.Changed += RequestRefresh;
|
||||
Volatile.Write(ref latest, await media.ReadAsync(cancellationToken));
|
||||
Volatile.Write(ref latest, await ReadSafeAsync(cancellationToken));
|
||||
await app.StartAsync(cancellationToken);
|
||||
polling = PollAsync(stop.Token);
|
||||
}
|
||||
@@ -134,7 +137,7 @@ internal sealed class RemoteServer : IAsyncDisposable
|
||||
// Coalesce bursts of Windows events without accumulating work.
|
||||
await Task.Delay(25, cancellationToken);
|
||||
while (refresh.Reader.TryRead(out _)) { }
|
||||
var state = await media.ReadAsync(cancellationToken);
|
||||
var state = await ReadSafeAsync(cancellationToken);
|
||||
Volatile.Write(ref latest, state);
|
||||
Published?.Invoke();
|
||||
}
|
||||
@@ -142,6 +145,16 @@ internal sealed class RemoteServer : IAsyncDisposable
|
||||
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { }
|
||||
}
|
||||
|
||||
private async Task<MediaState> ReadSafeAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
using var timeout = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
timeout.CancelAfter(TimeSpan.FromSeconds(5));
|
||||
try { return await media.ReadAsync(timeout.Token); }
|
||||
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { throw; }
|
||||
catch (OperationCanceledException) { return new MediaState { MediaError = "Плеер не ответил вовремя. Повторяем подключение." }; }
|
||||
catch (Exception ex) { return new MediaState { MediaError = $"Плеер временно недоступен: {ex.Message}" }; }
|
||||
}
|
||||
|
||||
// WSS is a state stream; commands use HTTPS so each has its own response.
|
||||
private async Task StreamAsync(HttpContext context)
|
||||
{
|
||||
@@ -195,7 +208,9 @@ internal sealed class RemoteServer : IAsyncDisposable
|
||||
while (pending.Reader.TryRead(out _)) { }
|
||||
if (!pairing.Authorize(token)) return;
|
||||
var bytes = JsonSerializer.SerializeToUtf8Bytes(new { type = "state", state = Latest }, JsonOptions);
|
||||
await socket.SendAsync(bytes.AsMemory(), WebSocketMessageType.Text, true, cancellationToken);
|
||||
using var sendTimeout = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
sendTimeout.CancelAfter(TimeSpan.FromSeconds(5));
|
||||
await socket.SendAsync(bytes.AsMemory(), WebSocketMessageType.Text, true, sendTimeout.Token);
|
||||
}
|
||||
}
|
||||
finally { Published -= Signal; pairing.TrustChanged -= Signal; }
|
||||
|
||||
Reference in New Issue
Block a user