diff --git a/SharpCaster.sln b/SharpCaster.sln index e912ef5..5014bf6 100644 --- a/SharpCaster.sln +++ b/SharpCaster.sln @@ -13,8 +13,8 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {7B2AED87-CDE7-4B71-A5B6-19512202FF60}.Debug|Any CPU.ActiveCfg = Release|Any CPU - {7B2AED87-CDE7-4B71-A5B6-19512202FF60}.Debug|Any CPU.Build.0 = Release|Any CPU + {7B2AED87-CDE7-4B71-A5B6-19512202FF60}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7B2AED87-CDE7-4B71-A5B6-19512202FF60}.Debug|Any CPU.Build.0 = Debug|Any CPU {7B2AED87-CDE7-4B71-A5B6-19512202FF60}.Release|Any CPU.ActiveCfg = Release|Any CPU {7B2AED87-CDE7-4B71-A5B6-19512202FF60}.Release|Any CPU.Build.0 = Release|Any CPU {C8C0A3A8-C6AC-4E1B-8D3B-E6764C45C35B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU diff --git a/Sharpcaster.Test/ChromecastConnectionTester.cs b/Sharpcaster.Test/ChromecastConnectionTester.cs index 05f39d2..3e1b4cc 100644 --- a/Sharpcaster.Test/ChromecastConnectionTester.cs +++ b/Sharpcaster.Test/ChromecastConnectionTester.cs @@ -1,11 +1,21 @@ -using System.Threading.Tasks; +using Microsoft.VisualStudio.TestPlatform.Utilities; +using System.Threading; +using System.Threading.Tasks; using Xunit; +using Xunit.Abstractions; namespace Sharpcaster.Test { [Collection("SingleCollection")] public class ChromecastConnectionTester { + + private ITestOutputHelper output; + public ChromecastConnectionTester(ITestOutputHelper outputHelper) + { + output = outputHelper; + } + [Fact] public async Task SearchChromecastsAndConnectToIt() { @@ -15,5 +25,26 @@ public async Task SearchChromecastsAndConnectToIt() Assert.NotNull(status); } + [Fact] + public async Task SearchChromecastsAndConnectToItThenWaitForItToShutdown() + { + var client = await TestHelper.CreateConnectAndLoadAppClient(output); + + Assert.NotNull(client.GetChromecastStatus()); + + AutoResetEvent _autoResetEvent = new AutoResetEvent(false); + + client.Disconnected += (sender, args) => + { + output.WriteLine("Chromecast did shutdown"); + _autoResetEvent.Set(); + }; + + //This checks that within 30 seconds we have noticed that device was turned off + //This need manual intervention to turn off the device + output.WriteLine("Waiting for Chromecast to shutdown"); + Assert.True(_autoResetEvent.WaitOne(30000)); + } + } } diff --git a/Sharpcaster/Channels/HeartbeatChannel.cs b/Sharpcaster/Channels/HeartbeatChannel.cs index a4cf0b4..392a4a0 100644 --- a/Sharpcaster/Channels/HeartbeatChannel.cs +++ b/Sharpcaster/Channels/HeartbeatChannel.cs @@ -1,7 +1,10 @@ -using Sharpcaster.Interfaces; +using Microsoft.Extensions.Logging; +using Sharpcaster.Interfaces; using Sharpcaster.Messages.Heartbeat; +using System; using System.Diagnostics; using System.Threading.Tasks; +using System.Timers; namespace Sharpcaster.Channels { @@ -10,28 +13,52 @@ namespace Sharpcaster.Channels /// public class HeartbeatChannel : ChromecastChannel, IHeartbeatChannel { + private ILogger _logger = null; + private Timer _timer; + /// /// Initializes a new instance of HeartbeatChannel class /// - public HeartbeatChannel() : base("tp.heartbeat") + public HeartbeatChannel(ILogger logger = null) : base("tp.heartbeat") { + _logger = logger; + _timer = new Timer(10000); // timeout is 10 seconds. + // Because Chromecast only waits for 8 seconds for response + _timer.Elapsed += TimerElapsed; + _timer.AutoReset = false; } + public event EventHandler StatusChanged; + /// /// Called when a message for this channel is received /// /// message to process public override async Task OnMessageReceivedAsync(IMessage message) { - if (message is PingMessage) - { - await SendAsync(new PongMessage()); - } - else - { - //TODO: Remove this if we don't hit this - Debugger.Break(); - } + _logger.LogDebug("Received ping message on Heartbeat channel"); + await SendAsync(new PongMessage()); + _logger.LogDebug("Sent pong message on Heartbeat channel"); + _timer.Stop(); + _timer.Start(); + } + + public void StartTimeoutTimer() + { + _timer.Start(); + _logger.LogDebug("Started heartbeat timeout timer"); + } + + public void StopTimeoutTimer() + { + _timer.Stop(); + _logger.LogDebug("Stopped heartbeat timeout timer"); + } + + private void TimerElapsed(object sender, ElapsedEventArgs e) + { + _logger.LogDebug("Heartbeat timeout"); + StatusChanged?.Invoke(this, e); } } } diff --git a/Sharpcaster/ChromeCastClient.cs b/Sharpcaster/ChromeCastClient.cs index 80f623d..bddd034 100644 --- a/Sharpcaster/ChromeCastClient.cs +++ b/Sharpcaster/ChromeCastClient.cs @@ -114,17 +114,26 @@ public async Task ConnectChromecast(ChromecastReceiver chromec _client = new TcpClient(); await _client.ConnectAsync(chromecastReceiver.DeviceUri.Host, chromecastReceiver.Port); + //Open SSL stream to Chromecast and bypass all SSL validation var secureStream = new SslStream(_client.GetStream(), true, (sender, certificate, chain, sslPolicyErrors) => true); await secureStream.AuthenticateAsClientAsync(chromecastReceiver.DeviceUri.Host); _stream = secureStream; + ReceiveTcs = new TaskCompletionSource(); Receive(); + GetChannel().StartTimeoutTimer(); + GetChannel().StatusChanged += HeartBeatTimedOut; await GetChannel().ConnectAsync(); return await GetChannel().GetChromecastStatusAsync(); } + private async void HeartBeatTimedOut(object sender, EventArgs e) + { + await DisconnectAsync(); + } + private void Receive() { Task.Run(async () => @@ -237,6 +246,8 @@ public async Task DisconnectAsync() { channel.GetType().GetProperty("Status").SetValue(channel, null); } + GetChannel().StopTimeoutTimer(); + GetChannel().StatusChanged -= HeartBeatTimedOut; await Dispose(); } diff --git a/Sharpcaster/Interfaces/IHeartbeatChannel.cs b/Sharpcaster/Interfaces/IHeartbeatChannel.cs index 9dfaf73..347d832 100644 --- a/Sharpcaster/Interfaces/IHeartbeatChannel.cs +++ b/Sharpcaster/Interfaces/IHeartbeatChannel.cs @@ -1,9 +1,20 @@ -namespace Sharpcaster.Interfaces +using System.Net.NetworkInformation; +using System; +using System.Threading.Tasks; +using System.Timers; + +namespace Sharpcaster.Interfaces { /// /// Interface for the heartbeat channel /// interface IHeartbeatChannel : IChromecastChannel { + void StartTimeoutTimer(); + void StopTimeoutTimer(); + /// + /// Raised when the status has changed + /// + event EventHandler StatusChanged; } }