Skip to content

Commit 795a42b

Browse files
committed
Implement progressive discovery for Chromecast devices
This commit introduces a new progressive discovery mechanism for finding Chromecast devices, replacing the previous single timeout approach. Key changes include updated method signatures, enhanced logging for the discovery process, and modifications to test cases to support the new functionality. Timeout values have been adjusted for a more flexible and efficient scanning process, improving the overall performance and reliability of device discovery.
1 parent f27b880 commit 795a42b

File tree

2 files changed

+168
-33
lines changed

2 files changed

+168
-33
lines changed

Sharpcaster.Test/MdnsChromecastLocatorTester.cs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Threading.Tasks;
55
using Xunit;
66
using Sharpcaster.Test.helper;
7+
using System.Linq;
78

89
namespace Sharpcaster.Test
910
{
@@ -28,10 +29,10 @@ public async Task SearchChromecastsTrickerEvent()
2829
};
2930

3031
// Start continuous discovery to trigger events
31-
locator.StartContinuousDiscovery(TimeSpan.FromSeconds(1));
32+
locator.StartContinuousDiscovery(TimeSpan.FromSeconds(5));
3233

3334
// Wait for events to be fired
34-
await Task.Delay(TimeSpan.FromSeconds(7), Xunit.TestContext.Current.CancellationToken);
35+
await Task.Delay(TimeSpan.FromMilliseconds(5100), Xunit.TestContext.Current.CancellationToken);
3536

3637
// Stop discovery
3738
locator.StopContinuousDiscovery();
@@ -46,7 +47,7 @@ public async Task SearchChromecastsTrickerEvent()
4647
public async Task SearchChromecastsWithTooShortTimeout()
4748
{
4849
var locator = new MdnsChromecastLocator();
49-
var chromecasts = await locator.FindReceiversAsync(TimeSpan.FromMicroseconds(1));
50+
var chromecasts = await locator.FindReceiversAsync(TimeSpan.FromMicroseconds(1), TimeSpan.FromMicroseconds(1), TimeSpan.FromMicroseconds(1));
5051
Assert.Empty(chromecasts);
5152
}
5253

@@ -57,5 +58,13 @@ public async Task SearchChromecastsWithTimeout()
5758
var chromecasts = await locator.FindReceiversAsync(TimeSpan.FromSeconds(5));
5859
Assert.NotEmpty(chromecasts);
5960
}
61+
62+
[Fact]
63+
public async Task TestNewProgressiveDiscovery()
64+
{
65+
var locator = new MdnsChromecastLocator();
66+
var chromecasts = await locator.FindReceiversAsync();
67+
Assert.Equal(4, chromecasts.Count());
68+
}
6069
}
6170
}

Sharpcaster/ChromecastLocator.cs

Lines changed: 156 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -53,49 +53,110 @@ public MdnsChromecastLocator(ILogger<MdnsChromecastLocator>? logger = null)
5353
}
5454

5555
/// <summary>
56-
/// Find the available chromecast receivers (async, no events)
56+
/// Find the available chromecast receivers using progressive discovery (async, no events)
57+
/// Performs quick scan first, then medium, then full timeout if needed
58+
/// Returns early if devices are found at any stage
5759
/// </summary>
58-
/// <param name="timeout">Discovery timeout (default 2 seconds)</param>
60+
/// <param name="quickTimeout">First scan timeout (default 400ms)</param>
61+
/// <param name="mediumTimeout">Second scan timeout (default 800ms)</param>
62+
/// <param name="fullTimeout">Final scan timeout (default 2 seconds)</param>
5963
/// <returns>Collection of chromecast receivers</returns>
60-
public async Task<IEnumerable<ChromecastReceiver>> FindReceiversAsync(TimeSpan? timeout = null)
64+
public async Task<IEnumerable<ChromecastReceiver>> FindReceiversAsync(
65+
TimeSpan? quickTimeout = null,
66+
TimeSpan? mediumTimeout = null,
67+
TimeSpan? fullTimeout = null)
6168
{
62-
var discoveryTimeout = timeout ?? TimeSpan.FromSeconds(2);
6369
var devices = new List<ChromecastReceiver>();
6470

65-
_mdnsDiscoveryStarted(_logger, discoveryTimeout.TotalMilliseconds, null);
71+
// Progressive scan timeouts: quick -> medium -> full
72+
var scanTimeouts = new[]
73+
{
74+
quickTimeout ?? TimeSpan.FromMilliseconds(400), // 1st scan: very quick
75+
mediumTimeout ?? TimeSpan.FromMilliseconds(800), // 2nd scan: medium speed
76+
fullTimeout ?? TimeSpan.FromSeconds(2) // 3rd scan: full timeout
77+
};
6678

67-
try
79+
_progressiveDiscoveryStarted(_logger, scanTimeouts.Length, scanTimeouts[scanTimeouts.Length - 1].TotalMilliseconds, null);
80+
81+
for (int scanIndex = 0; scanIndex < scanTimeouts.Length; scanIndex++)
6882
{
69-
var responses = await ZeroconfResolver.ResolveAsync(
70-
"_googlecast._tcp.local.",
71-
scanTime: discoveryTimeout).ConfigureAwait(false);
72-
73-
var responsesList = responses.ToList();
74-
_mdnsDiscoveryResponsesFound(_logger, responsesList.Count, null);
83+
var currentTimeout = scanTimeouts[scanIndex];
84+
var scanNumber = scanIndex + 1;
7585

76-
foreach (var response in responsesList)
86+
_progressiveCheckStarted(_logger, scanNumber, currentTimeout.TotalMilliseconds, null);
87+
88+
try
7789
{
78-
var chromecast = CreateChromecastReceiver(response);
79-
if (chromecast != null)
90+
var responses = await ZeroconfResolver.ResolveAsync(
91+
"_googlecast._tcp.local.",
92+
scanTime: currentTimeout).ConfigureAwait(false);
93+
94+
var responsesList = responses.ToList();
95+
96+
var scanDevices = new List<ChromecastReceiver>();
97+
foreach (var response in responsesList)
98+
{
99+
var chromecast = CreateChromecastReceiver(response);
100+
if (chromecast != null)
101+
{
102+
// Avoid duplicates from previous scans
103+
if (!devices.Any(d => d.DeviceUri.ToString() == chromecast.DeviceUri.ToString() && d.Port == chromecast.Port))
104+
{
105+
devices.Add(chromecast);
106+
scanDevices.Add(chromecast);
107+
_chromecastDiscovered(_logger, chromecast.Name, chromecast.DeviceUri.ToString(), chromecast.Port, null);
108+
}
109+
}
110+
}
111+
112+
_progressiveCheckCompleted(_logger, scanNumber, scanDevices.Count, devices.Count, null);
113+
114+
// If we found devices in this scan, stop here (early exit optimization)
115+
if (scanDevices.Count > 0)
80116
{
81-
devices.Add(chromecast);
82-
_chromecastDiscovered(_logger, chromecast.Name, chromecast.DeviceUri.ToString(), chromecast.Port, null);
117+
_progressiveDiscoveryCompletedEarly(_logger, scanNumber, devices.Count, null);
118+
break;
83119
}
120+
121+
_progressiveCheckWaiting(_logger, scanNumber, null);
122+
}
123+
catch (OperationCanceledException)
124+
{
125+
_progressiveCheckCancelled(_logger, scanNumber, devices.Count, null);
126+
break;
127+
}
128+
catch (TimeoutException ex)
129+
{
130+
_progressiveDiscoveryError(_logger, devices.Count, ex);
131+
// Continue to next scan on timeout
132+
}
133+
catch (Exception ex)
134+
{
135+
_progressiveCheckError(_logger, scanNumber, devices.Count, ex);
136+
// Continue to next scan on error
84137
}
85-
86-
_mdnsDiscoveryCompleted(_logger, devices.Count, null);
87-
}
88-
catch (OperationCanceledException ex)
89-
{
90-
_mdnsDiscoveryCancelled(_logger, devices.Count, ex);
91-
}
92-
catch (TimeoutException ex)
93-
{
94-
_mdnsDiscoveryTimedOut(_logger, devices.Count, ex);
95138
}
96-
catch (Exception ex)
139+
140+
_progressiveDiscoveryCompleted(_logger, devices.Count, null);
141+
return devices;
142+
}
143+
144+
/// <summary>
145+
/// Process discovery responses and convert to ChromecastReceiver objects
146+
/// </summary>
147+
private List<ChromecastReceiver> ProcessResponses(IEnumerable<IZeroconfHost> responses, ILogger logger)
148+
{
149+
var devices = new List<ChromecastReceiver>();
150+
var responsesList = responses.ToList();
151+
152+
foreach (var response in responsesList)
97153
{
98-
_mdnsDiscoveryError(_logger, devices.Count, ex);
154+
var chromecast = CreateChromecastReceiver(response);
155+
if (chromecast != null)
156+
{
157+
devices.Add(chromecast);
158+
_chromecastDiscovered(logger, chromecast.Name, chromecast.DeviceUri.ToString(), chromecast.Port, null);
159+
}
99160
}
100161

101162
return devices;
@@ -104,9 +165,13 @@ public async Task<IEnumerable<ChromecastReceiver>> FindReceiversAsync(TimeSpan?
104165
/// <summary>
105166
/// Start continuous discovery that raises events for found devices
106167
/// </summary>
107-
/// <param name="scanInterval">Time between scans (default 5 seconds)</param>
168+
/// <param name="scanInterval">Time between scans (default and minimium is 5 seconds)</param>
108169
public void StartContinuousDiscovery(TimeSpan? scanInterval = null)
109170
{
171+
if (scanInterval.HasValue && scanInterval.Value.TotalSeconds < 5)
172+
{
173+
throw new ArgumentException("Scan interval must be at least 5 seconds", nameof(scanInterval));
174+
}
110175
StopContinuousDiscovery();
111176

112177
var interval = scanInterval ?? TimeSpan.FromSeconds(5);
@@ -382,6 +447,67 @@ public void StopContinuousDiscovery()
382447
LogLevel.Warning,
383448
new EventId(1022, nameof(_unexpectedErrorDuringContinuousDiscoveryScan)),
384449
"Unexpected error during continuous discovery scan, retrying in 1 second");
450+
451+
// Progressive discovery logging delegates
452+
private static readonly Action<ILogger, int, double, Exception?> _progressiveDiscoveryStarted =
453+
LoggerMessage.Define<int, double>(
454+
LogLevel.Information,
455+
new EventId(1023, nameof(_progressiveDiscoveryStarted)),
456+
"Starting progressive mDNS discovery with {CheckCount} check intervals (max timeout: {MaxTimeout}ms)");
457+
458+
private static readonly Action<ILogger, int, double, Exception?> _progressiveCheckStarted =
459+
LoggerMessage.Define<int, double>(
460+
LogLevel.Debug,
461+
new EventId(1024, nameof(_progressiveCheckStarted)),
462+
"Progressive check {CheckNumber} at {Interval}ms - monitoring discovery progress");
463+
464+
private static readonly Action<ILogger, int, int, int, Exception?> _progressiveCheckCompleted =
465+
LoggerMessage.Define<int, int, int>(
466+
LogLevel.Debug,
467+
new EventId(1025, nameof(_progressiveCheckCompleted)),
468+
"Progressive check {CheckNumber} completed. Discovery finished with {NewDevices} devices ({TotalDevices} total)");
469+
470+
private static readonly Action<ILogger, int, int, Exception?> _progressiveDiscoveryCompletedEarly =
471+
LoggerMessage.Define<int, int>(
472+
LogLevel.Information,
473+
new EventId(1026, nameof(_progressiveDiscoveryCompletedEarly)),
474+
"Progressive discovery completed early at check {CheckNumber}. Found {DeviceCount} devices");
475+
476+
private static readonly Action<ILogger, int, Exception?> _progressiveCheckWaiting =
477+
LoggerMessage.Define<int>(
478+
LogLevel.Debug,
479+
new EventId(1027, nameof(_progressiveCheckWaiting)),
480+
"Progressive check {CheckNumber} - discovery still in progress, continuing to next interval");
481+
482+
private static readonly Action<ILogger, int, int, Exception?> _progressiveCheckCancelled =
483+
LoggerMessage.Define<int, int>(
484+
LogLevel.Warning,
485+
new EventId(1028, nameof(_progressiveCheckCancelled)),
486+
"Progressive check {CheckNumber} was cancelled. Returning {DeviceCount} devices found so far");
487+
488+
private static readonly Action<ILogger, int, int, Exception?> _progressiveCheckError =
489+
LoggerMessage.Define<int, int>(
490+
LogLevel.Warning,
491+
new EventId(1029, nameof(_progressiveCheckError)),
492+
"Error during progressive check {CheckNumber}. Continuing to next interval. Found {DeviceCount} devices so far");
493+
494+
private static readonly Action<ILogger, int, Exception?> _progressiveDiscoveryCancelled =
495+
LoggerMessage.Define<int>(
496+
LogLevel.Warning,
497+
new EventId(1030, nameof(_progressiveDiscoveryCancelled)),
498+
"Progressive discovery was cancelled. Returning {DeviceCount} devices found");
499+
500+
private static readonly Action<ILogger, int, Exception?> _progressiveDiscoveryError =
501+
LoggerMessage.Define<int>(
502+
LogLevel.Warning,
503+
new EventId(1031, nameof(_progressiveDiscoveryError)),
504+
"Error during progressive discovery final wait. Returning {DeviceCount} devices found");
505+
506+
private static readonly Action<ILogger, int, Exception?> _progressiveDiscoveryCompleted =
507+
LoggerMessage.Define<int>(
508+
LogLevel.Information,
509+
new EventId(1032, nameof(_progressiveDiscoveryCompleted)),
510+
"Progressive mDNS discovery completed. Found {DeviceCount} Chromecast devices total");
385511
#endregion
386512

387513
#region IDisposable Implementation

0 commit comments

Comments
 (0)