diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index fffc9aea..9150e73a 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -48,7 +48,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v4 with: - dotnet-version: 8.0.x + dotnet-version: 9.0.x - name: Install dependencies run: dotnet restore - name: Build diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 88de9f4d..a66de156 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -19,7 +19,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v4 with: - dotnet-version: 8.0.x + dotnet-version: 9.0.x - name: Restore dependencies run: dotnet restore SharpCaster.sln - name: tag diff --git a/SharpCaster.sln b/SharpCaster.sln index 5014bf65..a6c7152e 100644 --- a/SharpCaster.sln +++ b/SharpCaster.sln @@ -7,6 +7,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sharpcaster", "Sharpcaster\ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sharpcaster.Test", "Sharpcaster.Test\Sharpcaster.Test.csproj", "{C8C0A3A8-C6AC-4E1B-8D3B-E6764C45C35B}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sharpcaster.Test.Aot", "Sharpcaster.Test.Aot\Sharpcaster.Test.Aot.csproj", "{AA4C0985-3C97-496E-AF38-ADD0DC285A4C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -21,6 +23,10 @@ Global {C8C0A3A8-C6AC-4E1B-8D3B-E6764C45C35B}.Debug|Any CPU.Build.0 = Debug|Any CPU {C8C0A3A8-C6AC-4E1B-8D3B-E6764C45C35B}.Release|Any CPU.ActiveCfg = Release|Any CPU {C8C0A3A8-C6AC-4E1B-8D3B-E6764C45C35B}.Release|Any CPU.Build.0 = Release|Any CPU + {AA4C0985-3C97-496E-AF38-ADD0DC285A4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AA4C0985-3C97-496E-AF38-ADD0DC285A4C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AA4C0985-3C97-496E-AF38-ADD0DC285A4C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AA4C0985-3C97-496E-AF38-ADD0DC285A4C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Sharpcaster.Test.Aot/JsonTesting.cs b/Sharpcaster.Test.Aot/JsonTesting.cs new file mode 100644 index 00000000..f5ff9f89 --- /dev/null +++ b/Sharpcaster.Test.Aot/JsonTesting.cs @@ -0,0 +1,40 @@ +using Sharpcaster.Extensions; +using Sharpcaster.Interfaces; +using Sharpcaster.Messages.Connection; +using Sharpcaster.Messages.Heartbeat; +using System.Text.Json; + +namespace Sharpcaster.Test.Aot +{ + [TestClass] + public sealed class JsonTesting + { + [TestMethod] + public void TestConnectMessageSerialization() + { + IMessage connectMessage = new ConnectMessage(); + var requestId = ((IMessageWithId)connectMessage).RequestId; + + var output = JsonSerializer.Serialize(connectMessage, SharpcasteSerializationContext.Default.ConnectMessage); + Assert.AreEqual("{\"requestId\":" + requestId + ",\"type\":\"CONNECT\"}", output); + } + + [TestMethod] + public void TestPingMessageSerialization() + { + IMessage pingMessage = new PingMessage(); + + var output = JsonSerializer.Serialize(pingMessage, SharpcasteSerializationContext.Default.PingMessage); + Assert.AreEqual("{\"type\":\"PING\"}", output); + } + + [TestMethod] + public void TestPingMessageDeserialization() + { + const string input = "{\"type\":\"PING\"}"; + var message = JsonSerializer.Deserialize(input, SharpcasteSerializationContext.Default.PingMessage); + + Assert.AreEqual("PING", message?.Type); + } + } +} diff --git a/Sharpcaster.Test.Aot/MSTestSettings.cs b/Sharpcaster.Test.Aot/MSTestSettings.cs new file mode 100644 index 00000000..aaf278c8 --- /dev/null +++ b/Sharpcaster.Test.Aot/MSTestSettings.cs @@ -0,0 +1 @@ +[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)] diff --git a/Sharpcaster.Test.Aot/Sharpcaster.Test.Aot.csproj b/Sharpcaster.Test.Aot/Sharpcaster.Test.Aot.csproj new file mode 100644 index 00000000..4c6b1f6a --- /dev/null +++ b/Sharpcaster.Test.Aot/Sharpcaster.Test.Aot.csproj @@ -0,0 +1,40 @@ + + + + net9.0-windows + latest + enable + enable + true + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Sharpcaster.Test/ChromecastApplicationTester.cs b/Sharpcaster.Test/ChromecastApplicationTester.cs index 5c985292..236bd8d6 100644 --- a/Sharpcaster.Test/ChromecastApplicationTester.cs +++ b/Sharpcaster.Test/ChromecastApplicationTester.cs @@ -1,6 +1,8 @@ using Sharpcaster.Models; using Sharpcaster.Test.customChannel; +using Sharpcaster.Test.customMessage; using Sharpcaster.Test.helper; +using System.Text.Json; using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; @@ -18,7 +20,7 @@ public ChromecastApplicationTester(ITestOutputHelper outputHelper, ChromecastDev } [Theory] - [MemberData(nameof(ChromecastReceiversFilter.GetAll), MemberType = typeof(ChromecastReceiversFilter))] + [MemberData(nameof(ChromecastReceiversFilter.GetChromecastUltra), MemberType = typeof(ChromecastReceiversFilter))] public async Task ConnectToChromecastAndLaunchApplication(ChromecastReceiver receiver) { var TestHelper = new TestHelper(); @@ -31,7 +33,7 @@ public async Task ConnectToChromecastAndLaunchApplication(ChromecastReceiver rec } [Theory] - [MemberData(nameof(ChromecastReceiversFilter.GetAll), MemberType = typeof(ChromecastReceiversFilter))] + [MemberData(nameof(ChromecastReceiversFilter.GetChromecastUltra), MemberType = typeof(ChromecastReceiversFilter))] public async Task ConnectToChromecastAndLaunchApplicationTwice(ChromecastReceiver receiver) { var TestHelper = new TestHelper(); @@ -47,26 +49,8 @@ public async Task ConnectToChromecastAndLaunchApplicationTwice(ChromecastReceive Assert.Equal(firstLaunchTransportId, status.Application.TransportId); } - [Theory(Skip = "This does not pass any more. Now my JBL reacts as the other device - not changing the Transport ID !?")] - [MemberData(nameof(ChromecastReceiversFilter.GetJblSpeaker), MemberType = typeof(ChromecastReceiversFilter))] - public async Task ConnectToChromecastAndLaunchApplicationTwiceWithoutJoining1(ChromecastReceiver receiver) - { - var TestHelper = new TestHelper(); - var client = await TestHelper.CreateAndConnectClient(output, receiver); - var status = await client.LaunchApplicationAsync("B3419EF5"); - - var firstLaunchTransportId = status.Application.TransportId; - await client.DisconnectAsync(); - - _ = await client.ConnectChromecast(receiver); - status = await client.LaunchApplicationAsync("B3419EF5", false); - - // My JBL Device (almost every time - but not always ) makes a new ID here!!!! (The other device - ChromecastAudio DOES NOT!?) - Assert.NotEqual(firstLaunchTransportId, status.Application.TransportId); - } - [Theory] - [MemberData(nameof(ChromecastReceiversFilter.GetDefaultDevice), MemberType = typeof(ChromecastReceiversFilter))] + [MemberData(nameof(ChromecastReceiversFilter.GetAny), MemberType = typeof(ChromecastReceiversFilter))] public async Task ConnectToChromecastAndLaunchApplicationTwiceWithoutJoining2(ChromecastReceiver receiver) { var TestHelper = new TestHelper(); @@ -84,7 +68,7 @@ public async Task ConnectToChromecastAndLaunchApplicationTwiceWithoutJoining2(Ch } [Theory] - [MemberData(nameof(ChromecastReceiversFilter.GetAll), MemberType = typeof(ChromecastReceiversFilter))] + [MemberData(nameof(ChromecastReceiversFilter.GetChromecastUltra), MemberType = typeof(ChromecastReceiversFilter))] public async Task ConnectToChromecastAndLaunchApplicationAThenLaunchApplicationB(ChromecastReceiver receiver) { var TestHelper = new TestHelper(); @@ -101,7 +85,7 @@ public async Task ConnectToChromecastAndLaunchApplicationAThenLaunchApplicationB } [Theory] - [MemberData(nameof(ChromecastReceiversFilter.GetAll), MemberType = typeof(ChromecastReceiversFilter))] + [MemberData(nameof(ChromecastReceiversFilter.GetAny), MemberType = typeof(ChromecastReceiversFilter))] public async Task ConnectToChromecastAndLaunchApplicationOnceAndJoinIt(ChromecastReceiver receiver) { var TestHelper = new TestHelper(); @@ -129,7 +113,9 @@ public async Task ConnectToChromecastAndLaunchWebPage(ChromecastReceiver receive SessionId = client.GetChromecastStatus().Application.SessionId }; - await client.SendAsync(null, "urn:x-cast:com.boombatower.chromecast-dashboard", req, client.GetChromecastStatus().Application.SessionId); + var requestPayload = JsonSerializer.Serialize(req, WebMessageSerializationContext.Default.WebMessage); + + await client.SendAsync(null, "urn:x-cast:com.boombatower.chromecast-dashboard", requestPayload, client.GetChromecastStatus().Application.SessionId); await Task.Delay(5000); } } diff --git a/Sharpcaster.Test/ChromecastConnectionTester.cs b/Sharpcaster.Test/ChromecastConnectionTester.cs index 76cea1ce..d87c5330 100644 --- a/Sharpcaster.Test/ChromecastConnectionTester.cs +++ b/Sharpcaster.Test/ChromecastConnectionTester.cs @@ -5,8 +5,8 @@ using Xunit.Abstractions; using Sharpcaster.Test.helper; using Sharpcaster.Models.Media; -using System; using System.Linq; +using System.Collections.Generic; namespace Sharpcaster.Test { @@ -22,7 +22,7 @@ public ChromecastConnectionTester(ITestOutputHelper outputHelper, ChromecastDevi } [Theory] - [MemberData(nameof(ChromecastReceiversFilter.GetAll), MemberType = typeof(ChromecastReceiversFilter))] + [MemberData(nameof(ChromecastReceiversFilter.GetAny), MemberType = typeof(ChromecastReceiversFilter))] public async Task SearchChromecastsAndConnectToIt(ChromecastReceiver receiver) { var TestHelper = new TestHelper(); @@ -76,7 +76,7 @@ public async Task TestingHeartBeat(ChromecastReceiver receiver) int commandsToRun = 10; //We are setting up an event to listen to status change. Because we don't know when the video has started to play - client.MediaChannel.StatusChanged += (object sender, EventArgs e) => + client.MediaChannel.StatusChanged += (object sender, MediaStatus e) => { _autoResetEvent.Set(); }; diff --git a/Sharpcaster.Test/JsonTester.cs b/Sharpcaster.Test/JsonTester.cs new file mode 100644 index 00000000..ae432f8e --- /dev/null +++ b/Sharpcaster.Test/JsonTester.cs @@ -0,0 +1,25 @@ +using Microsoft.VisualBasic; +using Sharpcaster.Extensions; +using Sharpcaster.Interfaces; +using Sharpcaster.Messages.Connection; +using System.Text.Json; +using Xunit; + +namespace Sharpcaster.Test +{ + [Collection("SingleCollection")] + public class JsonTester + { + [Fact] + public void TestConnectMessage() + { + IMessage connectMessage = new ConnectMessage(); + var requestId = (connectMessage as IMessageWithId).RequestId; + + var output = JsonSerializer.Serialize(connectMessage, SharpcasteSerializationContext.Default.ConnectMessage); + Assert.Equal("{\"requestId\":" + requestId + ",\"type\":\"CONNECT\"}", output); + } + + + } +} diff --git a/Sharpcaster.Test/LoggingTester.cs b/Sharpcaster.Test/LoggingTester.cs index 67dbfe69..65c38ad0 100644 --- a/Sharpcaster.Test/LoggingTester.cs +++ b/Sharpcaster.Test/LoggingTester.cs @@ -20,11 +20,11 @@ public void TestLogging() List logLines = []; _ = TestHelper.GetClientWithTestOutput(output, assertableLog: logLines); - Assert.Equal("[addUserResponse,getInfoResponse,LAUNCH_ERROR,RECEIVER_STATUS,QUEUE_CHANGE,QUEUE_ITEM_IDS,QUEUE_ITEMS,DEVICE_UPDATED,MULTIZONE_STATUS,ERROR,INVALID_REQUEST,LOAD_CANCELLED,LOAD_FAILED,MEDIA_STATUS,PING,CLOSE]", logLines[0]); + Assert.Equal("MessageTypes: [addUserResponse,getInfoResponse,LAUNCH_ERROR,RECEIVER_STATUS,QUEUE_CHANGE,QUEUE_ITEM_IDS,QUEUE_ITEMS,DEVICE_UPDATED,MULTIZONE_STATUS,ERROR,INVALID_REQUEST,LOAD_CANCELLED,LOAD_FAILED,MEDIA_STATUS,PING,CLOSE,LAUNCH_STATUS]", logLines[0]); } [Theory] - [MemberData(nameof(ChromecastReceiversFilter.GetAll), MemberType = typeof(ChromecastReceiversFilter))] + [MemberData(nameof(ChromecastReceiversFilter.GetAny), MemberType = typeof(ChromecastReceiversFilter))] public async Task TestPlayMediaWorksWithoutLogging(ChromecastReceiver receiver) { ChromecastClient client = new(); diff --git a/Sharpcaster.Test/MediaChannelTester.cs b/Sharpcaster.Test/MediaChannelTester.cs index 6ed645ea..783bd83d 100644 --- a/Sharpcaster.Test/MediaChannelTester.cs +++ b/Sharpcaster.Test/MediaChannelTester.cs @@ -5,6 +5,7 @@ using Sharpcaster.Models.Queue; using Sharpcaster.Test.helper; using System; +using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -50,16 +51,9 @@ public async Task TestWaitForDeviceStopDuringPlayback(ChromecastReceiver receive AutoResetEvent _disconnectReceived = new(false); IMediaChannel mediaChannel = client.MediaChannel; - mediaChannel.StatusChanged += (object sender, EventArgs e) => + mediaChannel.StatusChanged += (object sender, MediaStatus e) => { - try - { - MediaStatus status = mediaChannel.MediaStatus; - output.WriteLine(status?.PlayerState.ToString()); - } - catch (Exception) - { - } + output.WriteLine(e.PlayerState.ToString()); }; client.Disconnected += (object sender, EventArgs e) => @@ -92,7 +86,7 @@ public async Task TestWaitForDeviceStopDuringPlayback(ChromecastReceiver receive } [Theory] - [MemberData(nameof(ChromecastReceiversFilter.GetChromecastUltra), MemberType = typeof(ChromecastReceiversFilter))] + [MemberData(nameof(ChromecastReceiversFilter.GetAny), MemberType = typeof(ChromecastReceiversFilter))] public async Task TestLoadingMediaQueueAndNavigateNextPrev(ChromecastReceiver receiver) { var TestHelper = new TestHelper(); @@ -106,12 +100,12 @@ public async Task TestLoadingMediaQueueAndNavigateNextPrev(ChromecastReceiver re var mediaStatusChanged = 0; //We are setting up an event to listen to status change. Because we don't know when the audio has started to play - mediaChannel.StatusChanged += async (object sender, EventArgs e) => + mediaChannel.StatusChanged += async (object sender, MediaStatus e) => { try { mediaStatusChanged += 1; - MediaStatus status = mediaChannel.MediaStatus; + MediaStatus status = e; int currentItemId = status?.CurrentItemId ?? -1; if (currentItemId != -1 && status.PlayerState == PlayerStateType.Playing) @@ -158,7 +152,7 @@ public async Task TestLoadingMediaQueueAndNavigateNextPrev(ChromecastReceiver re MediaStatus status = await client.MediaChannel.QueueLoadAsync(MyCd); Assert.Equal(PlayerStateType.Playing, status.PlayerState); - Assert.Equal(2, status.Items.Count()); // The status message only contains the next (and if available Prev) Track/QueueItem! + Assert.Equal(2, status.Items.Length); // The status message only contains the next (and if available Prev) Track/QueueItem! Assert.Equal(status.CurrentItemId, status.Items[0].ItemId); //This keeps the test running untill all eventhandler sequence steps are finished. If something goes wrong we get a very slow timeout here. @@ -178,7 +172,6 @@ public async Task TestLoadMediaQueueAndCheckContent(ChromecastReceiver receiver) QueueItem[] MyCd = helper.TestHelper.CreateTestCd; MediaStatus status = await client.MediaChannel.QueueLoadAsync(MyCd); - Assert.Equal(PlayerStateType.Playing, status.PlayerState); Assert.Equal(2, status.Items.Count()); // The status message only contains the next (and if available Prev) Track/QueueItem! Assert.Equal(status.CurrentItemId, status.Items[0].ItemId); @@ -201,7 +194,7 @@ public async Task TestLoadMediaQueueAndCheckContent(ChromecastReceiver receiver) } [Theory] - [MemberData(nameof(ChromecastReceiversFilter.GetAll), MemberType = typeof(ChromecastReceiversFilter))] + [MemberData(nameof(ChromecastReceiversFilter.GetAny), MemberType = typeof(ChromecastReceiversFilter))] public async Task TestLoadingMediaQueue(ChromecastReceiver receiver) { var TestHelper = new TestHelper(); @@ -238,7 +231,7 @@ public async Task TestLoadingMedia(ChromecastReceiver receiver) } [Theory] - [MemberData(nameof(ChromecastReceiversFilter.GetAll), MemberType = typeof(ChromecastReceiversFilter))] + [MemberData(nameof(ChromecastReceiversFilter.GetAny), MemberType = typeof(ChromecastReceiversFilter))] public async Task StartApplicationAThenStartBAndLoadMedia(ChromecastReceiver receiver) { var th = new TestHelper(); @@ -258,7 +251,7 @@ public async Task StartApplicationAThenStartBAndLoadMedia(ChromecastReceiver rec } [Theory] - [MemberData(nameof(ChromecastReceiversFilter.GetChromecastUltra), MemberType = typeof(ChromecastReceiversFilter))] + [MemberData(nameof(ChromecastReceiversFilter.GetAny), MemberType = typeof(ChromecastReceiversFilter))] public async Task TestLoadingAndPausingMedia(ChromecastReceiver receiver) { var TestHelper = new TestHelper(); @@ -275,10 +268,10 @@ public async Task TestLoadingAndPausingMedia(ChromecastReceiver receiver) bool firstPlay = true; //We are setting up an event to listen to status change. Because we don't know when the video has started to play - client.MediaChannel.StatusChanged += async (object sender, EventArgs e) => + client.MediaChannel.StatusChanged += async (object sender, MediaStatus e) => { //runSequence += "."; - if (client.MediaChannel.Status.FirstOrDefault()?.PlayerState == PlayerStateType.Playing) + if (e.PlayerState == PlayerStateType.Playing) { if (firstPlay) { @@ -305,7 +298,7 @@ public async Task TestLoadingAndPausingMedia(ChromecastReceiver receiver) } [Theory] - [MemberData(nameof(ChromecastReceiversFilter.GetChromecastUltra), MemberType = typeof(ChromecastReceiversFilter))] + [MemberData(nameof(ChromecastReceiversFilter.GetAny), MemberType = typeof(ChromecastReceiversFilter))] public async Task TestLoadingAndStoppingMedia(ChromecastReceiver receiver) { var TestHelper = new TestHelper(); @@ -321,11 +314,11 @@ public async Task TestLoadingAndStoppingMedia(ChromecastReceiver receiver) bool firstPlay = true; //We are setting up an event to listen to status change. Because we don't know when the video has started to play - client.MediaChannel.StatusChanged += async (object sender, EventArgs e) => + client.MediaChannel.StatusChanged += async (object sender, MediaStatus e) => { try { - if (client.MediaChannel.MediaStatus?.PlayerState == PlayerStateType.Playing) + if (e.PlayerState == PlayerStateType.Playing) { if (firstPlay) { @@ -363,23 +356,27 @@ public async Task TestFailingLoadMedia(ChromecastReceiver receiver) ContentUrl = "" }; - Exception loadFailedException = null; + LoadFailedMessage loadFailedMessage = null; - MediaStatus mediaStatus; - try - { - mediaStatus = await client.MediaChannel.LoadAsync(media); - } - catch (Exception ex) + client.MediaChannel.LoadFailed += (object sender, LoadFailedMessage e) => { - loadFailedException = ex; - } + loadFailedMessage = e; + }; + + + + MediaStatus mediaStatus; + + mediaStatus = await client.MediaChannel.LoadAsync(media); + + await Task.Delay(500); + - Assert.Equal("Load failed", loadFailedException?.Message); + Assert.NotNull(loadFailedMessage); } [Theory] - [MemberData(nameof(ChromecastReceiversFilter.GetChromecastUltra), MemberType = typeof(ChromecastReceiversFilter))] + [MemberData(nameof(ChromecastReceiversFilter.GetAny), MemberType = typeof(ChromecastReceiversFilter))] public async Task TestJoiningRunningMediaSessionAndPausingMedia(ChromecastReceiver receiver) { var TestHelper = new TestHelper(); @@ -391,8 +388,11 @@ public async Task TestJoiningRunningMediaSessionAndPausingMedia(ChromecastReceiv }; await client.MediaChannel.LoadAsync(media); + await client.MediaChannel.PlayAsync(); + await Task.Delay(500); + client = TestHelper.GetClientWithTestOutput(output); var status = await client.ConnectChromecast(receiver); @@ -401,8 +401,9 @@ public async Task TestJoiningRunningMediaSessionAndPausingMedia(ChromecastReceiv await client.MediaChannel.PauseAsync(); } + [Theory] - [MemberData(nameof(ChromecastReceiversFilter.GetChromecastUltra), MemberType = typeof(ChromecastReceiversFilter))] + [MemberData(nameof(ChromecastReceiversFilter.GetAny), MemberType = typeof(ChromecastReceiversFilter))] public async Task TestRepeatingAllQueueMedia(ChromecastReceiver receiver) { var TestHelper = new TestHelper(); @@ -425,7 +426,7 @@ public async Task TestRepeatingAllQueueMedia(ChromecastReceiver receiver) } [Theory] - [MemberData(nameof(ChromecastReceiversFilter.GetChromecastUltra), MemberType = typeof(ChromecastReceiversFilter))] + [MemberData(nameof(ChromecastReceiversFilter.GetAny), MemberType = typeof(ChromecastReceiversFilter))] public async Task TestRepeatingOffQueueMedia(ChromecastReceiver receiver) { var TestHelper = new TestHelper(); @@ -448,7 +449,7 @@ public async Task TestRepeatingOffQueueMedia(ChromecastReceiver receiver) } [Theory] - [MemberData(nameof(ChromecastReceiversFilter.GetChromecastUltra), MemberType = typeof(ChromecastReceiversFilter))] + [MemberData(nameof(ChromecastReceiversFilter.GetAny), MemberType = typeof(ChromecastReceiversFilter))] public async Task TestRepeatingSingleQueueMedia(ChromecastReceiver receiver) { var TestHelper = new TestHelper(); @@ -471,7 +472,7 @@ public async Task TestRepeatingSingleQueueMedia(ChromecastReceiver receiver) } [Theory] - [MemberData(nameof(ChromecastReceiversFilter.GetChromecastUltra), MemberType = typeof(ChromecastReceiversFilter))] + [MemberData(nameof(ChromecastReceiversFilter.GetAny), MemberType = typeof(ChromecastReceiversFilter))] public async Task TestRepeatingAllAndShuffleQueueMedia(ChromecastReceiver receiver) { var TestHelper = new TestHelper(); @@ -494,30 +495,32 @@ public async Task TestRepeatingAllAndShuffleQueueMedia(ChromecastReceiver receiv } [Theory] - [MemberData(nameof(ChromecastReceiversFilter.GetChromecastUltra), MemberType = typeof(ChromecastReceiversFilter))] + [MemberData(nameof(ChromecastReceiversFilter.GetAny), MemberType = typeof(ChromecastReceiversFilter))] public async Task TestFailingQueue(ChromecastReceiver receiver) { var TestHelper = new TestHelper(); ChromecastClient client = await TestHelper.CreateConnectAndLoadAppClient(output, receiver); bool errorHappened = false; - QueueItem[] MyCd = helper.TestHelper.CreateFailingQueu; + QueueItem[] MyCd = helper.TestHelper.CreateFailingQueue; - client.MediaChannel.ErrorHappened += (object sender, ErrorMessage e) => + + client.MediaChannel.InvalidRequest += (object sender, InvalidRequestMessage e) => { - output.WriteLine("Error happened: " + e.DetailedErrorCode); + output.WriteLine("Invalid Request Error happened: " + e.Reason); errorHappened = true; }; - try - { - var result = await client.MediaChannel.QueueLoadAsync(MyCd); - } - catch (Exception ex) + + client.MediaChannel.LoadFailed += (object sender, LoadFailedMessage e) => { - output.WriteLine("Exception happened: " + ex.Message); - } + output.WriteLine("Load Failed Error happened and failing media was " + e.ItemId); + errorHappened = true; + }; + + var result = await client.MediaChannel.QueueLoadAsync(MyCd); - await Task.Delay(200); + + await Task.Delay(2000); Assert.True(errorHappened); } } diff --git a/Sharpcaster.Test/MemoryAllocationTester.cs b/Sharpcaster.Test/MemoryAllocationTester.cs new file mode 100644 index 00000000..e2c47ec1 --- /dev/null +++ b/Sharpcaster.Test/MemoryAllocationTester.cs @@ -0,0 +1,44 @@ +using Sharpcaster.Test.helper; +using System; +using System.Linq; +using System.Threading.Tasks; +using Xunit; + +namespace Sharpcaster.Test +{ + [Collection("SingleCollection")] + public class MemoryAllocationTester + { + [Fact] + public async Task ConnectToChromecastAndLaunchApplication() + { + long memoryBefore = GC.GetTotalMemory(true); + MdnsChromecastLocator locator = new(); + var receivers = await locator.FindReceiversAsync(); + var TestHelper = new TestHelper(); + var client = new ChromecastClient(); + Assert.NotEmpty(receivers); + await client.ConnectChromecast(receivers.First()); + var status = await client.LaunchApplicationAsync("B3419EF5"); + + Assert.Equal("B3419EF5", status.Application.AppId); + + await client.DisconnectAsync(); + + long memoryAfter = GC.GetTotalMemory(true); + long memoryUsed = memoryAfter - memoryBefore; + double memoryUsedMB = memoryUsed / (1024.0 * 1024.0); + Console.WriteLine($"Memory used: {memoryUsedMB:F2} MB"); + + var memoryInfo = GC.GetGCMemoryInfo(); + var generations = memoryInfo.GenerationInfo; + + for (int i = 0; i < generations.Length; i++) + { + Console.WriteLine($"Generation {i}:"); + Console.WriteLine($" Size Before GC: {generations[i].SizeBeforeBytes / (1024.0 * 1024.0):F2} MB"); + Console.WriteLine($" Size After GC: {generations[i].SizeAfterBytes / (1024.0 * 1024.0):F2} MB"); + } + } + } +} diff --git a/Sharpcaster.Test/MultipleClientTester.cs b/Sharpcaster.Test/MultipleClientTester.cs index 3c7d2bf1..8a2e8582 100644 --- a/Sharpcaster.Test/MultipleClientTester.cs +++ b/Sharpcaster.Test/MultipleClientTester.cs @@ -20,7 +20,7 @@ public MultipleClientTester(ITestOutputHelper outputHelper, ChromecastDevicesFix } [Theory] - [MemberData(nameof(ChromecastReceiversFilter.GetChromecastUltra), MemberType = typeof(ChromecastReceiversFilter))] + [MemberData(nameof(ChromecastReceiversFilter.GetAny), MemberType = typeof(ChromecastReceiversFilter))] public async Task TestTwoClients(ChromecastReceiver receiver) { var TestHelper = new TestHelper(); @@ -46,7 +46,7 @@ public async Task TestTwoClients(ChromecastReceiver receiver) } [Theory] - [MemberData(nameof(ChromecastReceiversFilter.GetChromecastUltra), MemberType = typeof(ChromecastReceiversFilter))] + [MemberData(nameof(ChromecastReceiversFilter.GetAny), MemberType = typeof(ChromecastReceiversFilter))] public async Task TestThreeClients(ChromecastReceiver receiver) { var TestHelper = new TestHelper(); @@ -81,7 +81,7 @@ public async Task TestThreeClients(ChromecastReceiver receiver) } [Theory] - [MemberData(nameof(ChromecastReceiversFilter.GetChromecastUltra), MemberType = typeof(ChromecastReceiversFilter))] + [MemberData(nameof(ChromecastReceiversFilter.GetAny), MemberType = typeof(ChromecastReceiversFilter))] public async Task TestCommandsFromMultipleDifferentClients(ChromecastReceiver receiver) { var TestHelper = new TestHelper(); diff --git a/Sharpcaster.Test/ReceiverChannelTester.cs b/Sharpcaster.Test/ReceiverChannelTester.cs index 0e24293c..572a3e0d 100644 --- a/Sharpcaster.Test/ReceiverChannelTester.cs +++ b/Sharpcaster.Test/ReceiverChannelTester.cs @@ -18,7 +18,7 @@ public ReceiverChannelTester(ITestOutputHelper outputHelper, ChromecastDevicesFi } [Theory] - [MemberData(nameof(ChromecastReceiversFilter.GetAll), MemberType = typeof(ChromecastReceiversFilter))] + [MemberData(nameof(ChromecastReceiversFilter.GetAny), MemberType = typeof(ChromecastReceiversFilter))] public async Task TestMute(ChromecastReceiver receiver) { var TestHelper = new TestHelper(); @@ -30,7 +30,7 @@ public async Task TestMute(ChromecastReceiver receiver) } [Theory] - [MemberData(nameof(ChromecastReceiversFilter.GetAll), MemberType = typeof(ChromecastReceiversFilter))] + [MemberData(nameof(ChromecastReceiversFilter.GetAny), MemberType = typeof(ChromecastReceiversFilter))] public async Task TestUnMute(ChromecastReceiver receiver) { var TestHelper = new TestHelper(); @@ -41,7 +41,7 @@ public async Task TestUnMute(ChromecastReceiver receiver) } [Theory] - [MemberData(nameof(ChromecastReceiversFilter.GetAll), MemberType = typeof(ChromecastReceiversFilter))] + [MemberData(nameof(ChromecastReceiversFilter.GetAny), MemberType = typeof(ChromecastReceiversFilter))] public async Task TestVolume(ChromecastReceiver receiver) { var TestHelper = new TestHelper(); @@ -59,7 +59,7 @@ public async Task TestVolume(ChromecastReceiver receiver) } [Theory] - [MemberData(nameof(ChromecastReceiversFilter.GetAll), MemberType = typeof(ChromecastReceiversFilter))] + [MemberData(nameof(ChromecastReceiversFilter.GetAny), MemberType = typeof(ChromecastReceiversFilter))] public async Task TestStoppingApplication(ChromecastReceiver receiver) { var TestHelper = new TestHelper(); @@ -70,5 +70,25 @@ public async Task TestStoppingApplication(ChromecastReceiver receiver) var status = await client.ReceiverChannel.StopApplication(); Assert.Null(status.Applications); } + + [Theory] + [MemberData(nameof(ChromecastReceiversFilter.GetAny), MemberType = typeof(ChromecastReceiversFilter))] + public async Task TestApplicationLaunchStatusMessage(ChromecastReceiver receiver) + { + var TestHelper = new TestHelper(); + var client = await TestHelper.CreateAndConnectClient(output, receiver); + + string launchStatus = ""; + + client.ReceiverChannel.LaunchStatusChanged += (sender, e) => + { + launchStatus = e.Status; + }; + + await client.LaunchApplicationAsync("B3419EF5"); + + var status = await client.ReceiverChannel.StopApplication(); + Assert.Null(status.Applications); + } } } diff --git a/Sharpcaster.Test/Sharpcaster.Test.csproj b/Sharpcaster.Test/Sharpcaster.Test.csproj index fda1b7f0..82167e31 100644 --- a/Sharpcaster.Test/Sharpcaster.Test.csproj +++ b/Sharpcaster.Test/Sharpcaster.Test.csproj @@ -1,7 +1,7 @@  - net8.0 + net9.0 @@ -9,8 +9,9 @@ + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Sharpcaster.Test/SpotifyTester.cs b/Sharpcaster.Test/SpotifyTester.cs index 5e2a6456..44090eef 100644 --- a/Sharpcaster.Test/SpotifyTester.cs +++ b/Sharpcaster.Test/SpotifyTester.cs @@ -1,22 +1,14 @@ using Sharpcaster.Test.helper; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading.Tasks; using Xunit.Abstractions; using Xunit; -using Sharpcaster.Models.Media; using Sharpcaster.Models; -using System.Threading; using Sharpcaster.Channels; using System.Net.Http; using System.Net; using System.Text.Json.Nodes; using System.Net.Http.Json; -using System.ComponentModel.DataAnnotations; -using Sharpcaster.Messages.Spotify; -using Newtonsoft.Json; namespace Sharpcaster.Test { @@ -31,9 +23,7 @@ public SpotifyTester(ITestOutputHelper outputHelper, ChromecastDevicesFixture fi } [Theory] - //[MemberData(nameof(ChromecastReceiversFilter.GetGoogleCastGroup), MemberType = typeof(ChromecastReceiversFilter))] - //[MemberData(nameof(CCDevices.GetJblSpeaker), MemberType = typeof(CCDevices))] - [MemberData(nameof(ChromecastReceiversFilter.GetChromecastUltra), MemberType = typeof(ChromecastReceiversFilter))] + [MemberData(nameof(ChromecastReceiversFilter.GetAny), MemberType = typeof(ChromecastReceiversFilter))] public async Task TestChromecastGetInfo(ChromecastReceiver receiver) { var TestHelper = new TestHelper(); diff --git a/Sharpcaster.Test/customMessage/webMessage.cs b/Sharpcaster.Test/customMessage/webMessage.cs index ee52592f..f017d32d 100644 --- a/Sharpcaster.Test/customMessage/webMessage.cs +++ b/Sharpcaster.Test/customMessage/webMessage.cs @@ -1,12 +1,12 @@ using System.Runtime.Serialization; +using System.Text.Json.Serialization; using Sharpcaster.Messages; namespace Sharpcaster.Test.customChannel { - [DataContract] - internal class WebMessage : MessageWithSession + public class WebMessage : MessageWithSession { - [DataMember(Name = "url")] + [JsonPropertyName("url")] public string Url { get; set; } } } \ No newline at end of file diff --git a/Sharpcaster.Test/customMessage/webMessageSerializationContext.cs b/Sharpcaster.Test/customMessage/webMessageSerializationContext.cs new file mode 100644 index 00000000..879aab1e --- /dev/null +++ b/Sharpcaster.Test/customMessage/webMessageSerializationContext.cs @@ -0,0 +1,12 @@ +using Sharpcaster.Test.customChannel; +using System.Text.Json.Serialization; + +namespace Sharpcaster.Test.customMessage +{ + [JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(WebMessage))] + + public partial class WebMessageSerializationContext : JsonSerializerContext + { + } +} diff --git a/Sharpcaster.Test/helper/TestHelper.cs b/Sharpcaster.Test/helper/TestHelper.cs index be1aff53..9248d2db 100644 --- a/Sharpcaster.Test/helper/TestHelper.cs +++ b/Sharpcaster.Test/helper/TestHelper.cs @@ -326,7 +326,7 @@ public static QueueItem[] CreateTestCd } } - public static QueueItem[] CreateFailingQueu + public static QueueItem[] CreateFailingQueue { get { @@ -337,21 +337,36 @@ public static QueueItem[] CreateFailingQueu { Media = new Media { - ContentUrl = "https://audionautix.com/Music/AwayInAManger.mp3" + ContentUrl = "https://audionautix.com/Music/AwayInAManger.mp33", + Metadata = new MediaMetadata + { + Title = "Away In A Manger", + SubTitle = "Christmas Songs" + } } }, new QueueItem() { Media = new Media { - ContentUrl = "https://audionautix.com/Music/CarolOfTheBells.mp3" + ContentUrl = "https://audionautix.com/Music/CarolOfTheBells.mp33", + Metadata = new MediaMetadata + { + Title = "Carol Of The Bells", + SubTitle = "Christmas Songs" + } } }, new QueueItem() { Media = new Media { - ContentUrl = "https://audionautix.com/Music/JoyToTheWorld.mp3" + ContentUrl = "https://audionautix.com/Music/JoyToTheWorld.mp33", + Metadata = new MediaMetadata + { + Title = "Joy To The World", + SubTitle = "Christmas Songs" + } } } diff --git a/Sharpcaster/Channels/ChromecastChannel.cs b/Sharpcaster/Channels/ChromecastChannel.cs index 589e013a..7610dd63 100644 --- a/Sharpcaster/Channels/ChromecastChannel.cs +++ b/Sharpcaster/Channels/ChromecastChannel.cs @@ -52,28 +52,26 @@ protected ChromecastChannel(string ns, ILogger logger, bool useBaseNamespace = t /// /// message to send /// destination identifier - protected async Task SendAsync(IMessage message, string destinationId = DefaultIdentifiers.DESTINATION_ID) + protected async Task SendAsync(int messageRequestId, string messagePayload, string destinationId = DefaultIdentifiers.DESTINATION_ID) { - await Client.SendAsync(Logger, Namespace, message, destinationId); + return await Client.SendAsync(Logger, Namespace, messageRequestId, messagePayload, destinationId); } /// - /// Sends a message and waits the result + /// Sends a message /// - /// response type /// message to send /// destination identifier - /// the result - protected async Task SendAsync(IMessageWithId message, string destinationId = DefaultIdentifiers.DESTINATION_ID) where TResponse : IMessageWithId + protected async Task SendAsync(string messagePayload, string destinationId = DefaultIdentifiers.DESTINATION_ID) { - return await Client.SendAsync(Logger, Namespace, message, destinationId); + await Client.SendAsync(Logger, Namespace, messagePayload, destinationId); } /// /// Called when a message for this channel is received /// /// message to process - public virtual Task OnMessageReceivedAsync(IMessage message) + public virtual Task OnMessageReceivedAsync(string messagePayload, string type) { return Task.CompletedTask; } diff --git a/Sharpcaster/Channels/ConnectionChannel.cs b/Sharpcaster/Channels/ConnectionChannel.cs index e74a1488..c0bf7f06 100644 --- a/Sharpcaster/Channels/ConnectionChannel.cs +++ b/Sharpcaster/Channels/ConnectionChannel.cs @@ -1,6 +1,8 @@ using Microsoft.Extensions.Logging; +using Sharpcaster.Extensions; using Sharpcaster.Interfaces; using Sharpcaster.Messages.Connection; +using System.Text.Json; using System.Threading.Tasks; namespace Sharpcaster.Channels @@ -22,7 +24,7 @@ public ConnectionChannel(ILogger log = null) : base("tp.conne /// public async Task ConnectAsync() { - await SendAsync(new ConnectMessage()); + await ConnectAsync("receiver-0"); } /// @@ -30,21 +32,22 @@ public async Task ConnectAsync() /// public async Task ConnectAsync(string transportId) { - await SendAsync(new ConnectMessage(), transportId); + var connectMessage = new ConnectMessage(); + await SendAsync(JsonSerializer.Serialize(connectMessage, SharpcasteSerializationContext.Default.ConnectMessage), transportId); } /// /// Called when a message for this channel is received /// /// message to process - public async override Task OnMessageReceivedAsync(IMessage message) + public async override Task OnMessageReceivedAsync(string messagePayload, string type) { - if (message is CloseMessage) + if (type == "CLOSE") { // In order to avoid usage deadlocks we need to spawn a new Task here!? _ = Task.Run(async () => await Client.DisconnectAsync()); } - await base.OnMessageReceivedAsync(message); + await base.OnMessageReceivedAsync(messagePayload, type); } } } diff --git a/Sharpcaster/Channels/HeartbeatChannel.cs b/Sharpcaster/Channels/HeartbeatChannel.cs index e537e714..455828de 100644 --- a/Sharpcaster/Channels/HeartbeatChannel.cs +++ b/Sharpcaster/Channels/HeartbeatChannel.cs @@ -1,7 +1,9 @@ using Microsoft.Extensions.Logging; +using Sharpcaster.Extensions; using Sharpcaster.Interfaces; using Sharpcaster.Messages.Heartbeat; using System; +using System.Text.Json; using System.Threading.Tasks; using System.Timers; @@ -32,10 +34,11 @@ public HeartbeatChannel(ILogger logger = null) : base("tp.hear /// Called when a message for this channel is received /// /// message to process - public override async Task OnMessageReceivedAsync(IMessage message) + public override async Task OnMessageReceivedAsync(string messagePayload, string type) { _timer.Stop(); - await SendAsync(new PongMessage()); + var pongMessage = new PongMessage(); + await SendAsync(JsonSerializer.Serialize(pongMessage, SharpcasteSerializationContext.Default.PongMessage)); _timer.Start(); Logger?.LogDebug("Pong sent - Heartbeat Timer restarted."); } diff --git a/Sharpcaster/Channels/MediaChannel.cs b/Sharpcaster/Channels/MediaChannel.cs index 28225faf..c9df7deb 100644 --- a/Sharpcaster/Channels/MediaChannel.cs +++ b/Sharpcaster/Channels/MediaChannel.cs @@ -1,14 +1,16 @@ using Microsoft.Extensions.Logging; +using Sharpcaster.Extensions; using Sharpcaster.Interfaces; -using Sharpcaster.Messages; using Sharpcaster.Messages.Media; using Sharpcaster.Messages.Queue; +using Sharpcaster.Messages.Receiver; using Sharpcaster.Models.ChromecastStatus; using Sharpcaster.Models.Media; using Sharpcaster.Models.Queue; using System; -using System.Collections.Generic; +using System.Diagnostics; using System.Linq; +using System.Text.Json; using System.Threading.Tasks; namespace Sharpcaster.Channels @@ -16,13 +18,19 @@ namespace Sharpcaster.Channels /// /// Media channel /// - public class MediaChannel : StatusChannel>, IMediaChannel + public class MediaChannel : ChromecastChannel, IMediaChannel { /// /// Raised when error is received /// public event EventHandler ErrorHappened; - public MediaStatus MediaStatus { get => Status.FirstOrDefault(); } + public event EventHandler LoadCancelled; + public event EventHandler LoadFailed; + public event EventHandler InvalidRequest; + public event EventHandler StatusChanged; + + public MediaStatus MediaStatus { get => mediaStatus; } + private MediaStatus mediaStatus; /// /// Initializes a new instance of MediaChannel class /// @@ -30,25 +38,23 @@ public MediaChannel(ILogger logger = null) : base("media", logger) { } - private async Task SendAsync(IMessageWithId message, ChromecastApplication application) + private async Task SendAsync(int messageRequestId, string messagePayload, ChromecastApplication application, bool DoNotReturnOnLoading = true) { try { - var response = await SendAsync(message, application.TransportId); - if (response is LoadFailedMessage) - { - throw new Exception("Load failed"); - } - if (response is LoadCancelledMessage) + var response = await SendAsync(messageRequestId, messagePayload, application.TransportId); + var mediaStatusMessage = JsonSerializer.Deserialize(response, SharpcasteSerializationContext.Default.MediaStatusMessage); + if (DoNotReturnOnLoading && mediaStatusMessage.Status?.FirstOrDefault()?.ExtendedStatus?.PlayerState == PlayerStateType.Loading) { - throw new Exception("Load cancelled"); + response = await Client.WaitResponseAsync(messageRequestId); + mediaStatusMessage = JsonSerializer.Deserialize(response, SharpcasteSerializationContext.Default.MediaStatusMessage); } - return (response as MediaStatusMessage).Status?.FirstOrDefault(); + return mediaStatusMessage.Status?.FirstOrDefault(); } catch (Exception ex) { - Logger?.LogError($"Error sending message: {ex.Message}"); - Status = null; + Logger?.LogError("Error sending message: {exceptionMessage}", ex.Message); + mediaStatus = null; throw; } } @@ -56,8 +62,9 @@ private async Task SendAsync(IMessageWithId message, ChromecastAppl private async Task SendAsync(MediaSessionMessage message) { var chromecastStatus = Client.GetChromecastStatus(); - message.MediaSessionId = Status?.First().MediaSessionId ?? throw new ArgumentNullException("MediaSessionId"); - return await SendAsync(message, chromecastStatus.Applications[0]); + message.MediaSessionId = MediaStatus?.MediaSessionId ?? throw new ArgumentNullException(nameof(message), "MediaSessionID"); + var messagePayload = JsonSerializer.Serialize(message, SharpcasteSerializationContext.Default.MediaSessionMessage); + return await SendAsync(message.RequestId, messagePayload, chromecastStatus.Applications[0]); } /// @@ -69,18 +76,38 @@ private async Task SendAsync(MediaSessionMessage message) public async Task LoadAsync(Media media, bool autoPlay = true) { var status = Client.GetChromecastStatus(); - return await SendAsync(new LoadMessage() { SessionId = status.Application.SessionId, Media = media, AutoPlay = autoPlay }, status.Application); + var loadMessage = new LoadMessage() { SessionId = status.Application.SessionId, Media = media, AutoPlay = autoPlay }; + return await SendAsync(loadMessage.RequestId, JsonSerializer.Serialize(loadMessage, SharpcasteSerializationContext.Default.LoadMessage), status.Application); } - public override Task OnMessageReceivedAsync(IMessage message) + public override Task OnMessageReceivedAsync(string messagePayload, string type) { - switch (message) + switch (type) { - case ErrorMessage errorMessage: + case "LOAD_FAILED": + var loadFailedMessage = JsonSerializer.Deserialize(messagePayload, SharpcasteSerializationContext.Default.LoadFailedMessage); + LoadFailed?.Invoke(this, loadFailedMessage); + return Task.CompletedTask; + case "LOAD_CANCELLED": + var loadCancelledMessage = JsonSerializer.Deserialize(messagePayload, SharpcasteSerializationContext.Default.LoadCancelledMessage); + LoadCancelled?.Invoke(this, loadCancelledMessage); + return Task.CompletedTask; + case "INVALID_REQUEST": + var invalidRequestMessage = JsonSerializer.Deserialize(messagePayload, SharpcasteSerializationContext.Default.InvalidRequestMessage); + InvalidRequest?.Invoke(this, invalidRequestMessage); + return Task.CompletedTask; + case "ERROR": + var errorMessage = JsonSerializer.Deserialize(messagePayload, SharpcasteSerializationContext.Default.ErrorMessage); ErrorHappened?.Invoke(this, errorMessage); - throw new Exception("Errored: " + errorMessage.DetailedErrorCode); + return Task.CompletedTask; + case "MEDIA_STATUS": + var mediaStatusMessage = JsonSerializer.Deserialize(messagePayload, SharpcasteSerializationContext.Default.MediaStatusMessage); + mediaStatus = mediaStatusMessage.Status.FirstOrDefault(); + StatusChanged?.Invoke(this, MediaStatus); + return Task.CompletedTask; } - return base.OnMessageReceivedAsync(message); + Debugger.Break(); + return Task.CompletedTask; } /// @@ -107,7 +134,7 @@ public async Task PauseAsync() /// media status public async Task StopAsync() { - return await SendAsync(new StopMessage()); + return await SendAsync(new StopMediaMessage()); } /// @@ -123,18 +150,14 @@ public async Task SeekAsync(double seconds) public async Task QueueLoadAsync(QueueItem[] items, long? currentTime = null, RepeatModeType repeatMode = RepeatModeType.OFF, long? startIndex = null) { var chromecastStatus = Client.GetChromecastStatus(); - var response = await SendAsync(new QueueLoadMessage() { SessionId = chromecastStatus.Application.SessionId, Items = items, CurrentTime = currentTime, RepeatMode = repeatMode, StartIndex = startIndex }, chromecastStatus.Application.TransportId); - - switch (response) + var queueLoadMessage = new QueueLoadMessage() { SessionId = chromecastStatus.Application.SessionId, Items = items, CurrentTime = currentTime, RepeatMode = repeatMode, StartIndex = startIndex }; + var response = await SendAsync(queueLoadMessage.RequestId, JsonSerializer.Serialize(queueLoadMessage, SharpcasteSerializationContext.Default.QueueLoadMessage), chromecastStatus.Application.TransportId); + var mediaStatusMessage = JsonSerializer.Deserialize(response, SharpcasteSerializationContext.Default.MediaStatusMessage); + if (mediaStatusMessage != null) { - case MediaStatusMessage mediaStatusMessage: - return mediaStatusMessage.Status?.FirstOrDefault(); - case LoadFailedMessage _: - throw new Exception("Load failed"); - case LoadCancelledMessage _: - throw new Exception("Load cancelled"); + return mediaStatusMessage.Status.FirstOrDefault(); } - throw new Exception("Unknown response"); + return null; } public async Task QueueNextAsync() @@ -150,19 +173,28 @@ public async Task QueuePrevAsync() public async Task QueueGetItemsAsync(int[] ids = null) { var chromecastStatus = Client.GetChromecastStatus(); - return (await SendAsync(new QueueGetItemsMessage() { MediaSessionId = MediaStatus.MediaSessionId, Ids = ids }, chromecastStatus.Application.TransportId)).Items; + var queueGetItemsMessage = new QueueGetItemsMessage() { MediaSessionId = MediaStatus.MediaSessionId, Ids = ids }; + var response = await SendAsync(queueGetItemsMessage.RequestId, JsonSerializer.Serialize(queueGetItemsMessage, SharpcasteSerializationContext.Default.QueueGetItemsMessage), chromecastStatus.Application.TransportId); + var queueItemsResponse = JsonSerializer.Deserialize(response, SharpcasteSerializationContext.Default.QueueItemsMessage); + return queueItemsResponse.Items; } public async Task QueueGetItemIdsAsync() { var chromecastStatus = Client.GetChromecastStatus(); - return (await SendAsync(new QueueGetItemIdsMessage() { MediaSessionId = MediaStatus.MediaSessionId }, chromecastStatus.Application.TransportId)).Ids; + var queueGetItemIdsMessage = new QueueGetItemIdsMessage() { MediaSessionId = MediaStatus.MediaSessionId }; + var response = await SendAsync(queueGetItemIdsMessage.RequestId, JsonSerializer.Serialize(queueGetItemIdsMessage, SharpcasteSerializationContext.Default.QueueGetItemIdsMessage), chromecastStatus.Application.TransportId); + var queueItemIdsResponse = JsonSerializer.Deserialize(response, SharpcasteSerializationContext.Default.QueueItemIdsMessage); + return queueItemIdsResponse.Ids; } public async Task GetMediaStatusAsync() { var chromecastStatus = Client.GetChromecastStatus(); - return await SendAsync(new Messages.Receiver.GetStatusMessage(), chromecastStatus.Application); + var mediaStatusMessage = new GetStatusMessage(); + var response = await SendAsync(mediaStatusMessage.RequestId, JsonSerializer.Serialize(mediaStatusMessage, SharpcasteSerializationContext.Default.GetStatusMessage), chromecastStatus.Application.TransportId); + var mediaStatus = JsonSerializer.Deserialize(response, SharpcasteSerializationContext.Default.MediaStatusMessage); + return mediaStatus.Status.FirstOrDefault(); } } } diff --git a/Sharpcaster/Channels/MultiZoneChannel.cs b/Sharpcaster/Channels/MultiZoneChannel.cs index c7470939..9c511f5b 100644 --- a/Sharpcaster/Channels/MultiZoneChannel.cs +++ b/Sharpcaster/Channels/MultiZoneChannel.cs @@ -1,8 +1,8 @@ using Microsoft.Extensions.Logging; -using Sharpcaster.Interfaces; -using Sharpcaster.Messages.Multizone; +using Sharpcaster.Extensions; using Sharpcaster.Models.MultiZone; using System; +using System.Text.Json; using System.Threading.Tasks; namespace Sharpcaster.Channels @@ -29,22 +29,24 @@ public MultiZoneChannel(ILogger logger = null) : base("multizo /// Called when a message for this channel is received /// /// message to process - public override Task OnMessageReceivedAsync(IMessage message) + public override Task OnMessageReceivedAsync(string messagePayload, string type) { - switch (message) + switch (type) { - case MultizoneStatusMessage multizoneStatusMessage: + case "MULTIZONE_STATUS": + var multizoneStatusMessage = JsonSerializer.Deserialize(messagePayload, SharpcasteSerializationContext.Default.MultizoneStatusMessage); Status = multizoneStatusMessage.Status; StatusChanged?.Invoke(this, multizoneStatusMessage.Status); break; - case DeviceUpdatedMessage deviceUpdatedMessage: + case "DEVICE_UPDATED": + var deviceUpdatedMessage = JsonSerializer.Deserialize(messagePayload, SharpcasteSerializationContext.Default.DeviceUpdatedMessage); DeviceUpdated?.Invoke(this, deviceUpdatedMessage.Device); break; default: break; } - return base.OnMessageReceivedAsync(message); + return base.OnMessageReceivedAsync(messagePayload, type); } /// diff --git a/Sharpcaster/Channels/ReceiverChannel.cs b/Sharpcaster/Channels/ReceiverChannel.cs index 8b9d34e5..4077c27d 100644 --- a/Sharpcaster/Channels/ReceiverChannel.cs +++ b/Sharpcaster/Channels/ReceiverChannel.cs @@ -1,8 +1,10 @@ using Microsoft.Extensions.Logging; +using Sharpcaster.Extensions; using Sharpcaster.Interfaces; using Sharpcaster.Messages.Receiver; using Sharpcaster.Models.ChromecastStatus; using System; +using System.Text.Json; using System.Threading.Tasks; namespace Sharpcaster.Channels @@ -10,40 +12,78 @@ namespace Sharpcaster.Channels /// /// ReceiverChannel, Receives ChromecastStatus, volume, starting and stopping application /// - public class ReceiverChannel : StatusChannel, IReceiverChannel + public class ReceiverChannel : ChromecastChannel, IReceiverChannel { + public ChromecastStatus ReceiverStatus { get => receiverStatus; } + private ChromecastStatus receiverStatus; + public ReceiverChannel(ILogger logger = null) : base("receiver", logger) { } + public event EventHandler LaunchStatusChanged; + public event EventHandler ReceiverStatusChanged; public async Task GetChromecastStatusAsync() { - return (await SendAsync(new GetStatusMessage())).Status; + var getStatusMessage = new GetStatusMessage(); + var response = await SendAsync(getStatusMessage.RequestId, JsonSerializer.Serialize(getStatusMessage, SharpcasteSerializationContext.Default.GetStatusMessage)); + var status = JsonSerializer.Deserialize(response, SharpcasteSerializationContext.Default.ReceiverStatusMessage); + return status.Status; } public async Task LaunchApplicationAsync(string applicationId) { - return (await SendAsync(new LaunchMessage() { ApplicationId = applicationId })).Status; + var launchMessage = new LaunchMessage() { ApplicationId = applicationId }; + var response = await SendAsync(launchMessage.RequestId, JsonSerializer.Serialize(launchMessage, SharpcasteSerializationContext.Default.LaunchMessage)); + var status = JsonSerializer.Deserialize(response, SharpcasteSerializationContext.Default.ReceiverStatusMessage); + return status.Status; } public async Task SetMute(bool muted) { - return (await SendAsync(new SetVolumeMessage() { Volume = new Models.Volume() { Muted = muted } })).Status; + var setVolumeMessage = new SetVolumeMessage() { Volume = new Models.Volume() { Muted = muted } }; + var response = await SendAsync(setVolumeMessage.RequestId, JsonSerializer.Serialize(setVolumeMessage, SharpcasteSerializationContext.Default.SetVolumeMessage)); + var status = JsonSerializer.Deserialize(response, SharpcasteSerializationContext.Default.ReceiverStatusMessage); + return status.Status; } public async Task SetVolume(double level) { if (level < 0 || level > 1.0) { - Logger?.LogError($"level must be between 0.0 and 1.0 - is {level}"); + Logger?.LogError("level must be between 0.0 and 1.0 - is {level}", level); throw new ArgumentException("level must be between 0.0 and 1.0", nameof(level)); } - return (await SendAsync(new SetVolumeMessage() { Volume = new Models.Volume() { Level = level } })).Status; + var setVolumeMessage = new SetVolumeMessage() { Volume = new Models.Volume() { Level = level } }; + var response = await SendAsync(setVolumeMessage.RequestId, JsonSerializer.Serialize(setVolumeMessage, SharpcasteSerializationContext.Default.SetVolumeMessage)); + var status = JsonSerializer.Deserialize(response, SharpcasteSerializationContext.Default.ReceiverStatusMessage); + return status.Status; } public async Task StopApplication() { - return (await SendAsync(new StopMessage() { SessionId = Status.Application.SessionId })).Status; + var stopMessage = new StopMessage() { SessionId = ReceiverStatus.Application.SessionId }; + var response = await SendAsync(stopMessage.RequestId, JsonSerializer.Serialize(stopMessage, SharpcasteSerializationContext.Default.StopMessage)); + var status = JsonSerializer.Deserialize(response, SharpcasteSerializationContext.Default.ReceiverStatusMessage); + return status.Status; + } + + public override Task OnMessageReceivedAsync(string messagePayload, string type) + { + switch (type) + { + case "LAUNCH_STATUS": + var launchStatusMessage = JsonSerializer.Deserialize(messagePayload, SharpcasteSerializationContext.Default.LaunchStatusMessage); + LaunchStatusChanged?.Invoke(this, launchStatusMessage); + break; + case "RECEIVER_STATUS": + var receiverStatusMessage = JsonSerializer.Deserialize(messagePayload, SharpcasteSerializationContext.Default.ReceiverStatusMessage); + receiverStatus = receiverStatusMessage.Status; + ReceiverStatusChanged?.Invoke(this, ReceiverStatus); + break; + + } + return base.OnMessageReceivedAsync(messagePayload, type); } } } \ No newline at end of file diff --git a/Sharpcaster/Channels/SpotifyChannel.cs b/Sharpcaster/Channels/SpotifyChannel.cs index 08f91896..8058a0f0 100644 --- a/Sharpcaster/Channels/SpotifyChannel.cs +++ b/Sharpcaster/Channels/SpotifyChannel.cs @@ -1,10 +1,11 @@ using Microsoft.Extensions.Logging; -using Sharpcaster.Interfaces; +using Sharpcaster.Extensions; using Sharpcaster.Messages.Spotify; using Sharpcaster.Models.Spotify; using System; using System.Security.Cryptography; using System.Text; +using System.Text.Json; using System.Threading.Tasks; namespace Sharpcaster.Channels @@ -23,22 +24,22 @@ public SpotifyChannel(ILogger logger = null) : base("urn:x-cast: /// Called when a message for this channel is received /// /// message to process - public override Task OnMessageReceivedAsync(IMessage message) + public override Task OnMessageReceivedAsync(string messagePayload, string type) { - switch (message) + switch (messagePayload) { - case GetInfoResponseMessage getInfoResponseMessage: + case "getInfoResponse": + var getInfoResponseMessage = JsonSerializer.Deserialize(messagePayload, SharpcasteSerializationContext.Default.GetInfoResponseMessage); SpotifyStatus = getInfoResponseMessage.Payload; SpotifyStatusUpdated?.Invoke(this, getInfoResponseMessage.Payload); break; - case AddUserResponseMessage addUserResponseMessage: + case "addUserResponse": + var addUserResponseMessage = JsonSerializer.Deserialize(messagePayload, SharpcasteSerializationContext.Default.AddUserResponseMessage); AddUserResponseReceived?.Invoke(this, addUserResponseMessage.Payload); break; - default: - break; } - return base.OnMessageReceivedAsync(message); + return base.OnMessageReceivedAsync(messagePayload, type); } /// @@ -50,7 +51,7 @@ protected virtual void OnStatusChanged() public async Task GetSpotifyInfo() { - await SendAsync(new GetInfoMessage + var spotifyInfoMessage = new GetInfoMessage { Payload = new GetInfoMessagePayload { @@ -58,19 +59,21 @@ await SendAsync(new GetInfoMessage RemoteName = Client.FriendlyName, DeviceAPI_isGroup = false } - }, Client.GetChromecastStatus().Application.TransportId); + }; + await SendAsync(spotifyInfoMessage.RequestId, JsonSerializer.Serialize(spotifyInfoMessage, SharpcasteSerializationContext.Default.GetInfoMessage), Client.GetChromecastStatus().Application.TransportId); } public async Task AddUser(string accessToken) { - await SendAsync(new AddUserMessage + var addUserMessage = new AddUserMessage { Payload = new AddUserMessagePayload { Blob = accessToken, TokenType = "accesstoken" } - }, Client.GetChromecastStatus().Application.TransportId); + }; + await SendAsync(addUserMessage.RequestId, JsonSerializer.Serialize(addUserMessage, SharpcasteSerializationContext.Default.AddUserMessage), Client.GetChromecastStatus().Application.TransportId); } public string SpotifyDeviceId diff --git a/Sharpcaster/Channels/StatusChannel.cs b/Sharpcaster/Channels/StatusChannel.cs deleted file mode 100644 index aae7d4a3..00000000 --- a/Sharpcaster/Channels/StatusChannel.cs +++ /dev/null @@ -1,58 +0,0 @@ -using Microsoft.Extensions.Logging; -using Sharpcaster.Interfaces; -using System; -using System.Threading.Tasks; - -namespace Sharpcaster.Channels -{ - /// - /// Base class for status channels - /// - /// status message type - /// status type - public abstract class StatusChannel : ChromecastChannel, IStatusChannel where TStatusMessage : IStatusMessage - { - /// - /// Raised when the status has changed - /// - public event EventHandler StatusChanged; - - /// - /// Initialization - /// - /// namespace - protected StatusChannel(string ns, ILogger logger) : base(ns, logger) - { - } - - /// - /// Gets or sets the status - /// - public TStatus Status { get; protected set; } - - /// - /// Called when a message for this channel is received - /// - /// message to process - public override Task OnMessageReceivedAsync(IMessage message) - { - switch (message) - { - case TStatusMessage statusMessage: - Status = statusMessage.Status; - OnStatusChanged(); - break; - } - - return base.OnMessageReceivedAsync(message); - } - - /// - /// Raises the StatusChanged event - /// - protected virtual void OnStatusChanged() - { - StatusChanged?.Invoke(this, EventArgs.Empty); - } - } -} diff --git a/Sharpcaster/ChromeCastClient.cs b/Sharpcaster/ChromeCastClient.cs index 053bb9ce..b15aed79 100644 --- a/Sharpcaster/ChromeCastClient.cs +++ b/Sharpcaster/ChromeCastClient.cs @@ -1,11 +1,16 @@ using Extensions.Api.CastChannel; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using Newtonsoft.Json; using Sharpcaster.Channels; using Sharpcaster.Extensions; using Sharpcaster.Interfaces; -using Sharpcaster.Messages; +using Sharpcaster.Messages.Connection; +using Sharpcaster.Messages.Heartbeat; +using Sharpcaster.Messages.Media; +using Sharpcaster.Messages.Multizone; +using Sharpcaster.Messages.Queue; +using Sharpcaster.Messages.Receiver; +using Sharpcaster.Messages.Spotify; using Sharpcaster.Models; using Sharpcaster.Models.ChromecastStatus; using Sharpcaster.Models.Media; @@ -13,12 +18,11 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; -using System.IO; using System.Linq; using System.Net.Security; using System.Net.Sockets; -using System.Reflection; using System.Text; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; using static Extensions.Api.CastChannel.CastMessage.Types; @@ -44,13 +48,14 @@ public class ChromecastClient : IChromecastClient private ILogger _logger = null; private TcpClient _client; - private Stream _stream; + private SslStream _stream; + private CancellationTokenSource _cancellationTokenSource; private TaskCompletionSource ReceiveTcs { get; set; } private SemaphoreSlim SendSemaphoreSlim { get; } = new SemaphoreSlim(1, 1); - - private IDictionary MessageTypes { get; set; } + private JsonSerializerOptions _jsonSerializerOptions; + private Dictionary MessageTypes { get; set; } private IEnumerable Channels { get; set; } - private ConcurrentDictionary WaitingTasks { get; } = new ConcurrentDictionary(); + private ConcurrentDictionary WaitingTasks { get; } = new ConcurrentDictionary(); public ChromecastClient(ILoggerFactory loggerFactory = null) { @@ -69,12 +74,24 @@ public ChromecastClient(ILoggerFactory loggerFactory = null) serviceCollection.AddTransient(); serviceCollection.AddTransient(); var messageInterfaceType = typeof(IMessage); - foreach (var type in (from t in typeof(IConnectionChannel).GetTypeInfo().Assembly.GetTypes() - where t.GetTypeInfo().IsClass && !t.GetTypeInfo().IsAbstract && messageInterfaceType.IsAssignableFrom(t) && t.GetTypeInfo().GetCustomAttribute() != null - select t)) - { - serviceCollection.AddTransient(messageInterfaceType, type); - } + serviceCollection.AddTransient(messageInterfaceType, typeof(AddUserResponseMessage)); + serviceCollection.AddTransient(messageInterfaceType, typeof(GetInfoResponseMessage)); + serviceCollection.AddTransient(messageInterfaceType, typeof(LaunchErrorMessage)); + serviceCollection.AddTransient(messageInterfaceType, typeof(ReceiverStatusMessage)); + serviceCollection.AddTransient(messageInterfaceType, typeof(QueueChangeMessage)); + serviceCollection.AddTransient(messageInterfaceType, typeof(QueueItemIdsMessage)); + serviceCollection.AddTransient(messageInterfaceType, typeof(QueueItemsMessage)); + serviceCollection.AddTransient(messageInterfaceType, typeof(DeviceUpdatedMessage)); + serviceCollection.AddTransient(messageInterfaceType, typeof(MultizoneStatusMessage)); + serviceCollection.AddTransient(messageInterfaceType, typeof(ErrorMessage)); + serviceCollection.AddTransient(messageInterfaceType, typeof(InvalidRequestMessage)); + serviceCollection.AddTransient(messageInterfaceType, typeof(LoadCancelledMessage)); + serviceCollection.AddTransient(messageInterfaceType, typeof(LoadFailedMessage)); + serviceCollection.AddTransient(messageInterfaceType, typeof(MediaStatusMessage)); + serviceCollection.AddTransient(messageInterfaceType, typeof(PingMessage)); + serviceCollection.AddTransient(messageInterfaceType, typeof(CloseMessage)); + serviceCollection.AddTransient(messageInterfaceType, typeof(LaunchStatusMessage)); + Init(serviceCollection); } @@ -97,17 +114,26 @@ private void Init(IServiceCollection serviceCollection) Channels = channels; _logger = serviceProvider.GetService>(); - _logger?.LogDebug(MessageTypes.Keys.ToString(",")); - _logger?.LogDebug(Channels.ToString(",")); + _logger?.LogDebug("MessageTypes: {MessageTypes}", MessageTypes.Keys.ToString(",")); + _logger?.LogDebug("Channels: {Channels}", Channels.ToString(",")); foreach (var channel in Channels) { channel.Client = this; } + + _jsonSerializerOptions = new JsonSerializerOptions + { + TypeInfoResolver = SharpcasteSerializationContext.Default + }; } public async Task ConnectChromecast(ChromecastReceiver chromecastReceiver) { + if (chromecastReceiver.DeviceUri == null) + { + throw new ArgumentNullException(nameof(chromecastReceiver.DeviceUri)); + } await Dispose(); FriendlyName = chromecastReceiver.Name; @@ -119,8 +145,9 @@ public async Task ConnectChromecast(ChromecastReceiver chromec await secureStream.AuthenticateAsClientAsync(chromecastReceiver.DeviceUri.Host); _stream = secureStream; + _cancellationTokenSource = new CancellationTokenSource(); ReceiveTcs = new TaskCompletionSource(); - Receive(); + Receive(_cancellationTokenSource.Token); HeartbeatChannel.StartTimeoutTimer(); HeartbeatChannel.StatusChanged += HeartBeatTimedOut; await ConnectionChannel.ConnectAsync(); @@ -133,7 +160,7 @@ private async void HeartBeatTimedOut(object sender, EventArgs e) await DisconnectAsync(); } - private void Receive() + private void Receive(CancellationToken cancellationToken) { Task.Run(async () => { @@ -142,13 +169,13 @@ private void Receive() while (true) { //First 4 bytes contains the length of the message - var buffer = await _stream.ReadAsync(4); + var buffer = await _stream.ReadAsync(4, cancellationToken); if (BitConverter.IsLittleEndian) { Array.Reverse(buffer); } var length = BitConverter.ToInt32(buffer, 0); - var castMessage = CastMessage.Parser.ParseFrom(await _stream.ReadAsync(length)); + var castMessage = CastMessage.Parser.ParseFrom(await _stream.ReadAsync(length, cancellationToken)); //Payload can either be Binary or UTF8 json var payload = (castMessage.PayloadType == PayloadType.Binary ? Encoding.UTF8.GetString(castMessage.PayloadBinary.ToByteArray()) : castMessage.PayloadUtf8); @@ -160,22 +187,30 @@ private void Receive() { HeartbeatChannel.StopTimeoutTimer(); } - channel?.Logger?.LogTrace($"RECEIVED: {payload}"); + channel?.Logger?.LogTrace("RECEIVED: {payload}", payload); - var message = JsonConvert.DeserializeObject(payload); + var message = JsonSerializer.Deserialize(payload, SharpcasteSerializationContext.Default.MessageWithId); if (MessageTypes.TryGetValue(message.Type, out Type type)) { - object tcs = null; try { - var response = (IMessage)JsonConvert.DeserializeObject(payload, type); - await channel.OnMessageReceivedAsync(response); - TaskCompletionSourceInvoke(ref tcs, message, "SetResult", response); + await channel.OnMessageReceivedAsync(payload, message.Type); + if (message.HasRequestId) + { + WaitingTasks.TryRemove(message.RequestId, out SharpCasterTaskCompletionSource tcs); + tcs?.SetResult(payload); + if (tcs == null) + _logger.LogTrace("No TaskCompletionSource found for RequestId: {RequestId}, CompletionSourceCount: {CompletionSourceCount}, Type: {Type} ", message.RequestId, WaitingTasks.Count, message.Type); + } } catch (Exception ex) { - _logger?.LogError($"Exception processing the Response: {ex.Message}"); - TaskCompletionSourceInvoke(ref tcs, message, "SetException", ex, new Type[] { typeof(Exception) }); + _logger?.LogError("Exception processing the Response: {Message}", ex.Message); + if (message.HasRequestId) + { + WaitingTasks.TryRemove(message.RequestId, out SharpCasterTaskCompletionSource tcs); + tcs?.SetException(ex); + } } } else @@ -187,39 +222,23 @@ private void Receive() } else { - _logger?.LogError($"Couldn't parse the channel from: {castMessage.Namespace} : {payload}"); + _logger?.LogError("Couldn't parse the channel from: {NameSpace} : {Payload}", castMessage.Namespace, payload); } } } catch (Exception exception) { - _logger?.LogError($"Error in receive loop: {exception.Message}"); + _logger?.LogError("Error in receive loop: {Message}", exception.Message); //await Dispose(false); ReceiveTcs.SetResult(true); } - }); - } - - private void TaskCompletionSourceInvoke(ref object tcs, MessageWithId message, string method, object parameter, Type[] types = null) - { - if (tcs == null) - { - if (message.HasRequestId && WaitingTasks.TryRemove(message.RequestId, out object newtcs)) - { - tcs = newtcs; - } - } - if (tcs != null) - { - var tcsType = tcs.GetType(); - (types == null ? tcsType.GetMethod(method) : tcsType.GetMethod(method, types)).Invoke(tcs, new object[] { parameter }); - } + }, cancellationToken); } - public async Task SendAsync(ILogger channelLogger, string ns, IMessage message, string destinationId) + public async Task SendAsync(ILogger channelLogger, string ns, string messagePayload, string destinationId) { var castMessage = CreateCastMessage(ns, destinationId); - castMessage.PayloadUtf8 = JsonConvert.SerializeObject(message); + castMessage.PayloadUtf8 = messagePayload; await SendAsync(channelLogger, castMessage); } @@ -228,11 +247,18 @@ private async Task SendAsync(ILogger channelLogger, CastMessage castMessage) await SendSemaphoreSlim.WaitAsync(); try { - (channelLogger ?? _logger)?.LogTrace($"SENT : {castMessage.DestinationId}: {castMessage.PayloadUtf8}"); + (channelLogger ?? _logger)?.LogTrace("SENT: {NameSpace} - {DestinationId}: {PayloadUtf8}", castMessage.Namespace, castMessage.DestinationId, castMessage.PayloadUtf8); +#if NETSTANDARD2_0 byte[] message = castMessage.ToProto(); - var networkStream = _stream; - await networkStream.WriteAsync(message, 0, message.Length); - await networkStream.FlushAsync(); +#else + ReadOnlyMemory message = castMessage.ToProto(); +#endif +#if NETSTANDARD2_0 + await _stream.WriteAsync(message, 0, message.Length); +#else + await _stream.WriteAsync(message); +#endif + await _stream.FlushAsync(); } finally { @@ -250,11 +276,18 @@ private CastMessage CreateCastMessage(string ns, string destinationId) }; } - public async Task SendAsync(ILogger channelLogger, string ns, IMessageWithId message, string destinationId) where TResponse : IMessageWithId + public async Task SendAsync(ILogger channelLogger, string ns, int messageRequestId, string messagePayload, string destinationId) + { + var taskCompletionSource = new SharpCasterTaskCompletionSource(); + WaitingTasks[messageRequestId] = taskCompletionSource; + await SendAsync(channelLogger, ns, messagePayload, destinationId); + return await taskCompletionSource.Task.TimeoutAfter(RECEIVE_TIMEOUT); + } + + public async Task WaitResponseAsync(int messageRequestId) { - var taskCompletionSource = new TaskCompletionSource(); - WaitingTasks[message.RequestId] = taskCompletionSource; - await SendAsync(channelLogger, ns, message, destinationId); + var taskCompletionSource = new SharpCasterTaskCompletionSource(); + WaitingTasks[messageRequestId] = taskCompletionSource; return await taskCompletionSource.Task.TimeoutAfter(RECEIVE_TIMEOUT); } @@ -262,10 +295,12 @@ public async Task DisconnectAsync() { foreach (var channel in GetStatusChannels()) { - channel.GetType().GetProperty("Status").SetValue(channel, null); + channel.ClearStatus(); } HeartbeatChannel.StopTimeoutTimer(); HeartbeatChannel.StatusChanged -= HeartBeatTimedOut; + _cancellationTokenSource.Cancel(true); + await Task.Delay(100); await Dispose(); } @@ -299,7 +334,7 @@ private void Dispose(IDisposable disposable, Action action) } catch (Exception ex) { - _logger?.LogError($"Error on disposing. {ex.Message}"); + _logger?.LogError("Error on disposing. {Message}", ex.Message); } finally { @@ -349,10 +384,9 @@ public async Task LaunchApplicationAsync(string applicationId, return await ReceiverChannel.GetChromecastStatusAsync(); } - private IEnumerable GetStatusChannels() + private IEnumerable> GetStatusChannels() { - var statusChannelType = typeof(IStatusChannel<>); - return Channels.Where(c => c.GetType().GetInterfaces().Any(i => i.GetTypeInfo().IsGenericType && i.GetGenericTypeDefinition() == statusChannelType)); + return Channels.OfType>(); } /// @@ -361,12 +395,12 @@ private IEnumerable GetStatusChannels() /// a dictionnary of namespace/status public IDictionary GetStatuses() { - return GetStatusChannels().ToDictionary(c => c.Namespace, c => c.GetType().GetProperty("Status").GetValue(c)); + return GetStatusChannels().ToDictionary(c => c.Namespace, c => c.Status); } public ChromecastStatus GetChromecastStatus() { - return ReceiverChannel.Status; + return ReceiverChannel.ReceiverStatus; } public MediaStatus GetMediaStatus() diff --git a/Sharpcaster/ChromecastLocator.cs b/Sharpcaster/ChromecastLocator.cs index ea2b3c4d..1e5d746b 100644 --- a/Sharpcaster/ChromecastLocator.cs +++ b/Sharpcaster/ChromecastLocator.cs @@ -15,9 +15,12 @@ namespace Sharpcaster public class MdnsChromecastLocator : IChromecastLocator { public event EventHandler ChromecastReceivedFound; - private IList DiscoveredDevices { get; } + private List DiscoveredDevices { get; } private readonly ServiceBrowser _serviceBrowser; private SemaphoreSlim ServiceAddedSemaphoreSlim { get; } = new SemaphoreSlim(1, 1); + + private static readonly string[] stringArray = new string[] { "", "" }; + public MdnsChromecastLocator() { DiscoveredDevices = new List(); @@ -43,10 +46,10 @@ private void OnServiceAdded(object sender, ServiceAnnouncementEventArgs e) { return i.Split('='); } - return new string[] { "", "" }; + return stringArray; }) .ToDictionary(y => y[0], y => y[1]); - if (!txtValues.ContainsKey("fn")) return; + if (!txtValues.TryGetValue("fn", out string value)) return; var ip = e.Announcement.Addresses[0]; var uriBuilder = new UriBuilder("https", ip.ToString()); Uri myUri = uriBuilder.Uri; @@ -54,7 +57,7 @@ private void OnServiceAdded(object sender, ServiceAnnouncementEventArgs e) var chromecast = new ChromecastReceiver { DeviceUri = myUri, - Name = txtValues["fn"], + Name = value, Model = txtValues["md"], Version = txtValues["ve"], ExtraInformation = txtValues, @@ -92,7 +95,7 @@ public async Task> FindReceiversAsync(Cancellati _serviceBrowser.StartBrowse("_googlecast._tcp"); while (!cancellationToken.IsCancellationRequested) { - await Task.Delay(100); + await Task.Delay(100, CancellationToken.None); } _serviceBrowser.StopBrowse(); return DiscoveredDevices; diff --git a/Sharpcaster/Converters/PlayerStateEnumConverter.cs b/Sharpcaster/Converters/PlayerStateEnumConverter.cs index 319d70de..c0205a63 100644 --- a/Sharpcaster/Converters/PlayerStateEnumConverter.cs +++ b/Sharpcaster/Converters/PlayerStateEnumConverter.cs @@ -1,59 +1,55 @@ -using Newtonsoft.Json; +using System; +using System.Text.Json; +using System.Text.Json.Serialization; using Sharpcaster.Models.Media; -using System; namespace Sharpcaster.Converters { - public class PlayerStateEnumConverter : JsonConverter + public class PlayerStateEnumConverter : JsonConverter { - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + public override PlayerStateType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - var playerState = (PlayerStateType)value; - switch (playerState) + string enumString = reader.GetString(); + switch (enumString) { - case PlayerStateType.Buffering: - writer.WriteValue("BUFFERING"); - break; - case PlayerStateType.Idle: - writer.WriteValue("IDLE"); - break; - case PlayerStateType.Paused: - writer.WriteValue("PAUSED"); - break; - case PlayerStateType.Playing: - writer.WriteValue("PLAYING"); - break; + case "BUFFERING": + return PlayerStateType.Buffering; + case "IDLE": + return PlayerStateType.Idle; + case "PAUSED": + return PlayerStateType.Paused; + case "PLAYING": + return PlayerStateType.Playing; + case "LOADING": + return PlayerStateType.Loading; + default: - throw new ArgumentOutOfRangeException(); + throw new JsonException($"Invalid value '{enumString}' for {nameof(PlayerStateType)}"); } } - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + public override void Write(Utf8JsonWriter writer, PlayerStateType value, JsonSerializerOptions options) { - var enumString = (string)reader.Value; - PlayerStateType? playerState = null; - - switch (enumString) + switch (value) { - case "BUFFERING": - playerState = PlayerStateType.Buffering; + case PlayerStateType.Buffering: + writer.WriteStringValue("BUFFERING"); break; - case "IDLE": - playerState = PlayerStateType.Idle; + case PlayerStateType.Idle: + writer.WriteStringValue("IDLE"); break; - case "PAUSED": - playerState = PlayerStateType.Paused; + case PlayerStateType.Paused: + writer.WriteStringValue("PAUSED"); break; - case "PLAYING": - playerState = PlayerStateType.Playing; + case PlayerStateType.Playing: + writer.WriteStringValue("PLAYING"); break; + case PlayerStateType.Loading: + writer.WriteStringValue("LOADING"); + break; + default: + throw new JsonException($"Unsupported {nameof(PlayerStateType)} value: {value}"); } - return playerState; - } - - public override bool CanConvert(Type objectType) - { - return objectType == typeof(string); } } } \ No newline at end of file diff --git a/Sharpcaster/Converters/RepeatModeEnumConverter.cs b/Sharpcaster/Converters/RepeatModeEnumConverter.cs index 4bc7d57a..5644cb1e 100644 --- a/Sharpcaster/Converters/RepeatModeEnumConverter.cs +++ b/Sharpcaster/Converters/RepeatModeEnumConverter.cs @@ -1,59 +1,52 @@ -using Newtonsoft.Json; +using System; +using System.Text.Json; +using System.Text.Json.Serialization; using Sharpcaster.Models.Media; -using System; namespace Sharpcaster.Converters { - public class RepeatModeEnumConverter : JsonConverter + public class RepeatModeEnumConverter : JsonConverter { - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + public override RepeatModeType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - var repeatModeType = (RepeatModeType)value; - switch (repeatModeType) + string enumString = reader.GetString(); + switch (enumString) { - case RepeatModeType.OFF: - writer.WriteValue("REPEAT_OFF"); - break; - case RepeatModeType.ALL: - writer.WriteValue("REPEAT_ALL"); - break; - case RepeatModeType.SINGLE: - writer.WriteValue("REPEAT_SINGLE"); - break; - case RepeatModeType.ALL_AND_SHUFFLE: - writer.WriteValue("REPEAT_ALL_AND_SHUFFLE"); - break; + case "REPEAT_OFF": + return RepeatModeType.OFF; + case "REPEAT_ALL": + return RepeatModeType.ALL; + case "REPEAT_SINGLE": + return RepeatModeType.SINGLE; + case "REPEAT_ALL_AND_SHUFFLE": + return RepeatModeType.ALL_AND_SHUFFLE; default: - throw new ArgumentOutOfRangeException(); + throw new JsonException($"Invalid value '{enumString}' for {nameof(RepeatModeType)}"); } } - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + public override void Write(Utf8JsonWriter writer, RepeatModeType value, JsonSerializerOptions options) { - var enumString = (string)reader.Value; - RepeatModeType? repeatModeType = null; - - switch (enumString) + string stringValue; + switch (value) { - case "REPEAT_OFF": - repeatModeType = RepeatModeType.OFF; + case RepeatModeType.OFF: + stringValue = "REPEAT_OFF"; break; - case "REPEAT_ALL": - repeatModeType = RepeatModeType.ALL; + case RepeatModeType.ALL: + stringValue = "REPEAT_ALL"; break; - case "REPEAT_SINGLE": - repeatModeType = RepeatModeType.SINGLE; + case RepeatModeType.SINGLE: + stringValue = "REPEAT_SINGLE"; break; - case "REPEAT_ALL_AND_SHUFFLE": - repeatModeType = RepeatModeType.ALL_AND_SHUFFLE; + case RepeatModeType.ALL_AND_SHUFFLE: + stringValue = "REPEAT_ALL_AND_SHUFFLE"; break; + default: + throw new JsonException($"Unsupported {nameof(RepeatModeType)} value: {value}"); } - return repeatModeType; - } - public override bool CanConvert(Type objectType) - { - return objectType == typeof(string); + writer.WriteStringValue(stringValue); } } -} +} \ No newline at end of file diff --git a/Sharpcaster/Extensions/SharpcasteSerialiationContext.cs b/Sharpcaster/Extensions/SharpcasteSerialiationContext.cs new file mode 100644 index 00000000..b8321416 --- /dev/null +++ b/Sharpcaster/Extensions/SharpcasteSerialiationContext.cs @@ -0,0 +1,52 @@ +using Sharpcaster.Interfaces; +using Sharpcaster.Messages; +using Sharpcaster.Messages.Connection; +using Sharpcaster.Messages.Heartbeat; +using Sharpcaster.Messages.Media; +using Sharpcaster.Messages.Multizone; +using Sharpcaster.Messages.Queue; +using Sharpcaster.Messages.Receiver; +using Sharpcaster.Messages.Spotify; +using System.Text.Json.Serialization; + +namespace Sharpcaster.Extensions +{ + + [JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(ConnectMessage))] + [JsonSerializable(typeof(PingMessage))] + [JsonSerializable(typeof(PongMessage))] + [JsonSerializable(typeof(IMessage))] + [JsonSerializable(typeof(Message))] + [JsonSerializable(typeof(MessageWithId))] + [JsonSerializable(typeof(GetStatusMessage))] + [JsonSerializable(typeof(ReceiverStatusMessage))] + [JsonSerializable(typeof(LaunchMessage))] + [JsonSerializable(typeof(LaunchStatusMessage))] + [JsonSerializable(typeof(LaunchErrorMessage))] + [JsonSerializable(typeof(StopMediaMessage))] + [JsonSerializable(typeof(StopMessage))] + [JsonSerializable(typeof(MediaSessionMessage))] + [JsonSerializable(typeof(MediaStatusMessage))] + [JsonSerializable(typeof(MultizoneStatusMessage))] + [JsonSerializable(typeof(CloseMessage))] + [JsonSerializable(typeof(LoadMessage))] + [JsonSerializable(typeof(QueueLoadMessage))] + [JsonSerializable(typeof(QueueGetItemsMessage))] + [JsonSerializable(typeof(QueueItemsMessage))] + [JsonSerializable(typeof(QueueGetItemIdsMessage))] + [JsonSerializable(typeof(QueueItemIdsMessage))] + [JsonSerializable(typeof(AddUserResponseMessage))] + [JsonSerializable(typeof(GetInfoResponseMessage))] + [JsonSerializable(typeof(GetInfoMessage))] + [JsonSerializable(typeof(AddUserMessage))] + [JsonSerializable(typeof(DeviceUpdatedMessage))] + [JsonSerializable(typeof(LoadFailedMessage))] + [JsonSerializable(typeof(LoadCancelledMessage))] + [JsonSerializable(typeof(InvalidRequestMessage))] + [JsonSerializable(typeof(ErrorMessage))] + [JsonSerializable(typeof(SetVolumeMessage))] + public partial class SharpcasteSerializationContext : JsonSerializerContext + { + } +} diff --git a/Sharpcaster/Extensions/SslStreamExtensions.cs b/Sharpcaster/Extensions/SslStreamExtensions.cs index f0bb06a8..77744ad4 100644 --- a/Sharpcaster/Extensions/SslStreamExtensions.cs +++ b/Sharpcaster/Extensions/SslStreamExtensions.cs @@ -1,25 +1,37 @@ using System; using System.IO; +using System.Net.Security; +using System.Threading; using System.Threading.Tasks; namespace Sharpcaster.Extensions { public static class StreamExtensions { - public static async Task ReadAsync(this Stream stream, int bufferLength) + public static async Task ReadAsync(this SslStream stream, int bufferLength, CancellationToken cancellationToken = default) { var buffer = new byte[bufferLength]; - int nb, length = 0; - while (length < bufferLength) + + #if NETSTANDARD2_0 + int bytesRead, totalBytesRead = 0; + while (totalBytesRead < bufferLength) { - nb = await stream.ReadAsync(buffer, length, bufferLength - length); - if (nb == 0) + if (stream == null) + { + throw new ObjectDisposedException(nameof(stream)); + } + bytesRead = await stream.ReadAsync(buffer, totalBytesRead, bufferLength - totalBytesRead, cancellationToken); + if (bytesRead == 0) { - throw new InvalidOperationException(); + throw new EndOfStreamException(); } - length += nb; + totalBytesRead += bytesRead; } + #else + ObjectDisposedException.ThrowIf(stream == null, stream); + await stream.ReadExactlyAsync(buffer.AsMemory(0, bufferLength), cancellationToken); + #endif return buffer; } } -} +} \ No newline at end of file diff --git a/Sharpcaster/Interfaces/IChromecastChannel.cs b/Sharpcaster/Interfaces/IChromecastChannel.cs index 64b68c08..99c2a6d0 100644 --- a/Sharpcaster/Interfaces/IChromecastChannel.cs +++ b/Sharpcaster/Interfaces/IChromecastChannel.cs @@ -23,7 +23,7 @@ public interface IChromecastChannel /// /// Called when a message for this channel is received /// - /// message to process - Task OnMessageReceivedAsync(IMessage message); + /// message to process + Task OnMessageReceivedAsync(string messagePayload, string type); } } diff --git a/Sharpcaster/Interfaces/IChromecastClient.cs b/Sharpcaster/Interfaces/IChromecastClient.cs index f3bf3eb8..2da46dbe 100644 --- a/Sharpcaster/Interfaces/IChromecastClient.cs +++ b/Sharpcaster/Interfaces/IChromecastClient.cs @@ -6,10 +6,10 @@ namespace Sharpcaster.Interfaces { public interface IChromecastClient { - //TODO: Write summaries here - Task SendAsync(ILogger logger, string ns, IMessage message, string destinationId); Task LaunchApplicationAsync(string applicationId, bool joinExistingApplicationSession = true); - Task SendAsync(ILogger logger, string ns, IMessageWithId message, string destinationId) where TResponse : IMessageWithId; + Task SendAsync(ILogger logger, string ns, int messageRequestId, string messagePayload, string destinationId); + Task SendAsync(ILogger logger, string ns, string messagePayload, string destinationId); + Task WaitResponseAsync(int messageRequestId); Task DisconnectAsync(); ChromecastStatus GetChromecastStatus(); string FriendlyName { get; set; } diff --git a/Sharpcaster/Interfaces/IMediaChannel.cs b/Sharpcaster/Interfaces/IMediaChannel.cs index 8b81ae67..da36984d 100644 --- a/Sharpcaster/Interfaces/IMediaChannel.cs +++ b/Sharpcaster/Interfaces/IMediaChannel.cs @@ -3,6 +3,7 @@ using Sharpcaster.Models.Queue; using System; using System.Collections.Generic; +using System.Net.NetworkInformation; using System.Threading.Tasks; namespace Sharpcaster.Interfaces @@ -10,9 +11,13 @@ namespace Sharpcaster.Interfaces /// /// Interface for the media channel /// - public interface IMediaChannel : IStatusChannel>, IChromecastChannel + public interface IMediaChannel : IChromecastChannel { event EventHandler ErrorHappened; + event EventHandler LoadFailed; + event EventHandler LoadCancelled; + event EventHandler InvalidRequest; + event EventHandler StatusChanged; /// /// Loads a media /// diff --git a/Sharpcaster/Interfaces/IMessage.cs b/Sharpcaster/Interfaces/IMessage.cs index 111c3338..7f4fab17 100644 --- a/Sharpcaster/Interfaces/IMessage.cs +++ b/Sharpcaster/Interfaces/IMessage.cs @@ -1,4 +1,6 @@ -namespace Sharpcaster.Interfaces +using System.Text.Json.Serialization; + +namespace Sharpcaster.Interfaces { /// /// Interface for a message @@ -8,6 +10,7 @@ public interface IMessage /// /// Gets the message type /// + [JsonPropertyName("type")] string Type { get; } } } diff --git a/Sharpcaster/Interfaces/IMessageWithId.cs b/Sharpcaster/Interfaces/IMessageWithId.cs index 2d24dfb9..3e4229ff 100644 --- a/Sharpcaster/Interfaces/IMessageWithId.cs +++ b/Sharpcaster/Interfaces/IMessageWithId.cs @@ -1,4 +1,6 @@ -namespace Sharpcaster.Interfaces +using System.Text.Json.Serialization; + +namespace Sharpcaster.Interfaces { /// /// Interface for messages with request identifier @@ -8,6 +10,7 @@ public interface IMessageWithId : IMessage /// /// Gets the request identifier /// + [JsonPropertyName("requestId")] int RequestId { get; } } } diff --git a/Sharpcaster/Interfaces/IReceiverChannel.cs b/Sharpcaster/Interfaces/IReceiverChannel.cs index df51e37f..0234966b 100644 --- a/Sharpcaster/Interfaces/IReceiverChannel.cs +++ b/Sharpcaster/Interfaces/IReceiverChannel.cs @@ -1,4 +1,6 @@ -using Sharpcaster.Models.ChromecastStatus; +using Sharpcaster.Messages.Receiver; +using Sharpcaster.Models.ChromecastStatus; +using System; using System.Threading.Tasks; namespace Sharpcaster.Interfaces @@ -6,7 +8,7 @@ namespace Sharpcaster.Interfaces /// /// Interface for the receiver channel /// - public interface IReceiverChannel : IStatusChannel, IChromecastChannel + public interface IReceiverChannel : IChromecastChannel { /// /// Launches an application @@ -14,10 +16,14 @@ public interface IReceiverChannel : IStatusChannel, IChromecas /// application identifier /// receiver status Task LaunchApplicationAsync(string applicationId); - Task GetChromecastStatusAsync(); Task StopApplication(); Task SetVolume(double level); Task SetMute(bool muted); + + ChromecastStatus ReceiverStatus { get; } + + event EventHandler LaunchStatusChanged; + event EventHandler ReceiverStatusChanged; } } diff --git a/Sharpcaster/Interfaces/IStatusChannel.cs b/Sharpcaster/Interfaces/IStatusChannel.cs index e2dd09bb..202f87e8 100644 --- a/Sharpcaster/Interfaces/IStatusChannel.cs +++ b/Sharpcaster/Interfaces/IStatusChannel.cs @@ -11,11 +11,16 @@ public interface IStatusChannel : IChromecastChannel /// /// Raised when the status has changed /// - event EventHandler StatusChanged; + event EventHandler StatusChanged; /// /// Gets the status /// TStatus Status { get; } + + /// + /// Clears the status + /// + void ClearStatus(); } } diff --git a/Sharpcaster/Messages/Chromecast/StatusMessage.cs b/Sharpcaster/Messages/Chromecast/StatusMessage.cs index 333fc1c9..555e06d3 100644 --- a/Sharpcaster/Messages/Chromecast/StatusMessage.cs +++ b/Sharpcaster/Messages/Chromecast/StatusMessage.cs @@ -1,5 +1,5 @@ using Sharpcaster.Interfaces; -using System.Runtime.Serialization; +using System.Text.Json.Serialization; namespace Sharpcaster.Messages.Chromecast { @@ -7,14 +7,13 @@ namespace Sharpcaster.Messages.Chromecast /// Status message base class /// /// status type - [DataContract] [ReceptionMessage] public abstract class StatusMessage : MessageWithId, IStatusMessage { /// /// Gets or sets the status /// - [DataMember(Name = "status")] + [JsonPropertyName("status")] public TStatus Status { get; set; } } } diff --git a/Sharpcaster/Messages/Connection/CloseMessage.cs b/Sharpcaster/Messages/Connection/CloseMessage.cs index 3b084195..1fa98d90 100644 --- a/Sharpcaster/Messages/Connection/CloseMessage.cs +++ b/Sharpcaster/Messages/Connection/CloseMessage.cs @@ -1,11 +1,8 @@ -using System.Runtime.Serialization; - -namespace Sharpcaster.Messages.Connection +namespace Sharpcaster.Messages.Connection { /// /// Close message /// - [DataContract] [ReceptionMessage] public class CloseMessage : Message { diff --git a/Sharpcaster/Messages/Connection/ConnectMessage.cs b/Sharpcaster/Messages/Connection/ConnectMessage.cs index 0920f7ab..deafeca0 100644 --- a/Sharpcaster/Messages/Connection/ConnectMessage.cs +++ b/Sharpcaster/Messages/Connection/ConnectMessage.cs @@ -1,11 +1,8 @@ -using System.Runtime.Serialization; - -namespace Sharpcaster.Messages.Connection +namespace Sharpcaster.Messages.Connection { /// /// Connect message /// - [DataContract] public class ConnectMessage : MessageWithId { } diff --git a/Sharpcaster/Messages/Heartbeat/PingMessage.cs b/Sharpcaster/Messages/Heartbeat/PingMessage.cs index 2bac3d27..f24a1731 100644 --- a/Sharpcaster/Messages/Heartbeat/PingMessage.cs +++ b/Sharpcaster/Messages/Heartbeat/PingMessage.cs @@ -1,11 +1,8 @@ -using System.Runtime.Serialization; - -namespace Sharpcaster.Messages.Heartbeat +namespace Sharpcaster.Messages.Heartbeat { /// /// Ping message /// - [DataContract] [ReceptionMessage] public class PingMessage : Message { diff --git a/Sharpcaster/Messages/Heartbeat/PongMessage.cs b/Sharpcaster/Messages/Heartbeat/PongMessage.cs index e990f69d..62238c92 100644 --- a/Sharpcaster/Messages/Heartbeat/PongMessage.cs +++ b/Sharpcaster/Messages/Heartbeat/PongMessage.cs @@ -1,11 +1,8 @@ -using System.Runtime.Serialization; - -namespace Sharpcaster.Messages.Heartbeat +namespace Sharpcaster.Messages.Heartbeat { /// /// Pong message /// - [DataContract] public class PongMessage : Message { } diff --git a/Sharpcaster/Messages/Media/ErrorMessage.cs b/Sharpcaster/Messages/Media/ErrorMessage.cs index 015c63c5..74e98a35 100644 --- a/Sharpcaster/Messages/Media/ErrorMessage.cs +++ b/Sharpcaster/Messages/Media/ErrorMessage.cs @@ -1,14 +1,13 @@ -using System.Runtime.Serialization; +using System.Text.Json.Serialization; namespace Sharpcaster.Messages.Media { - [DataContract] [ReceptionMessage] public class ErrorMessage : Message { - [DataMember(Name = "detailedErrorCode")] + [JsonPropertyName("detailedErrorCode")] public int DetailedErrorCode { get; set; } - [DataMember(Name = "itemId")] + [JsonPropertyName("itemId")] public int ItemId { get; set; } } } diff --git a/Sharpcaster/Messages/Media/InvalidRequestMessage.cs b/Sharpcaster/Messages/Media/InvalidRequestMessage.cs index d6757c70..ea2c3dea 100644 --- a/Sharpcaster/Messages/Media/InvalidRequestMessage.cs +++ b/Sharpcaster/Messages/Media/InvalidRequestMessage.cs @@ -1,25 +1,17 @@ -using System; -using System.Runtime.Serialization; +using System.Text.Json.Serialization; namespace Sharpcaster.Messages.Media { /// /// Invalid request message /// - [DataContract] [ReceptionMessage] public class InvalidRequestMessage : MessageWithId { /// /// Gets or sets the reason /// - [DataMember(Name = "reason")] + [JsonPropertyName("reason")] public string Reason { get; set; } - - [OnDeserialized] - private void OnDeserialized(StreamingContext context) - { - throw new InvalidOperationException(Reason); - } } } diff --git a/Sharpcaster/Messages/Media/LoadCancelledMessage.cs b/Sharpcaster/Messages/Media/LoadCancelledMessage.cs index 0b895715..8fa7377d 100644 --- a/Sharpcaster/Messages/Media/LoadCancelledMessage.cs +++ b/Sharpcaster/Messages/Media/LoadCancelledMessage.cs @@ -1,19 +1,10 @@ -using System; -using System.Runtime.Serialization; - -namespace Sharpcaster.Messages.Media +namespace Sharpcaster.Messages.Media { /// /// Load cancelled message /// - [DataContract] [ReceptionMessage] public class LoadCancelledMessage : MessageWithId { - [OnDeserializing] - private void OnDeserializing(StreamingContext context) - { - throw new Exception("Load cancelled"); - } } } diff --git a/Sharpcaster/Messages/Media/LoadFailedMessage.cs b/Sharpcaster/Messages/Media/LoadFailedMessage.cs index 3479639b..7e75db42 100644 --- a/Sharpcaster/Messages/Media/LoadFailedMessage.cs +++ b/Sharpcaster/Messages/Media/LoadFailedMessage.cs @@ -1,13 +1,14 @@ -using System.Runtime.Serialization; +using System.Text.Json.Serialization; namespace Sharpcaster.Messages.Media { /// /// Load failed message /// - [DataContract] [ReceptionMessage] public class LoadFailedMessage : MessageWithId { + [JsonPropertyName("itemId")] + public int ItemId { get; set; } } } diff --git a/Sharpcaster/Messages/Media/LoadMessage.cs b/Sharpcaster/Messages/Media/LoadMessage.cs index 5606ea21..202ec509 100644 --- a/Sharpcaster/Messages/Media/LoadMessage.cs +++ b/Sharpcaster/Messages/Media/LoadMessage.cs @@ -1,23 +1,22 @@ -using System.Runtime.Serialization; +using System.Text.Json.Serialization; namespace Sharpcaster.Messages.Media { /// /// Load message /// - [DataContract] public class LoadMessage : MessageWithSession { /// /// Gets or sets the media /// - [DataMember(Name = "media")] + [JsonPropertyName("media")] public Models.Media.Media Media { get; set; } /// /// Gets or sets a value indicating whether the media must be played directly or not /// - [DataMember(Name = "autoplay")] + [JsonPropertyName("autoplay")] public bool AutoPlay { get; set; } } } diff --git a/Sharpcaster/Messages/Media/MediaSessionMessage.cs b/Sharpcaster/Messages/Media/MediaSessionMessage.cs index 0b39eb82..be682d0f 100644 --- a/Sharpcaster/Messages/Media/MediaSessionMessage.cs +++ b/Sharpcaster/Messages/Media/MediaSessionMessage.cs @@ -1,17 +1,16 @@ -using System.Runtime.Serialization; +using System.Text.Json.Serialization; namespace Sharpcaster.Messages.Media { /// /// Media session message /// - [DataContract] public abstract class MediaSessionMessage : MessageWithId { /// /// Gets or sets the media session identifier /// - [DataMember(Name = "mediaSessionId")] + [JsonPropertyName("mediaSessionId")] public long MediaSessionId { get; set; } } } diff --git a/Sharpcaster/Messages/Media/MediaStatusMessage.cs b/Sharpcaster/Messages/Media/MediaStatusMessage.cs index d546b225..5294f12b 100644 --- a/Sharpcaster/Messages/Media/MediaStatusMessage.cs +++ b/Sharpcaster/Messages/Media/MediaStatusMessage.cs @@ -1,14 +1,12 @@ using Sharpcaster.Messages.Chromecast; using Sharpcaster.Models.Media; using System.Collections.Generic; -using System.Runtime.Serialization; namespace Sharpcaster.Messages.Media { /// /// Media status message /// - [DataContract] [ReceptionMessage] public class MediaStatusMessage : StatusMessage> { diff --git a/Sharpcaster/Messages/Media/PauseMessage.cs b/Sharpcaster/Messages/Media/PauseMessage.cs index 4c4bf79d..0ceef48b 100644 --- a/Sharpcaster/Messages/Media/PauseMessage.cs +++ b/Sharpcaster/Messages/Media/PauseMessage.cs @@ -1,11 +1,8 @@ -using System.Runtime.Serialization; - -namespace Sharpcaster.Messages.Media +namespace Sharpcaster.Messages.Media { /// /// Pause message /// - [DataContract] public class PauseMessage : MediaSessionMessage { } diff --git a/Sharpcaster/Messages/Media/PlayMessage.cs b/Sharpcaster/Messages/Media/PlayMessage.cs index 55f4994e..b5b5e051 100644 --- a/Sharpcaster/Messages/Media/PlayMessage.cs +++ b/Sharpcaster/Messages/Media/PlayMessage.cs @@ -1,11 +1,8 @@ -using System.Runtime.Serialization; - -namespace Sharpcaster.Messages.Media +namespace Sharpcaster.Messages.Media { /// /// Play message /// - [DataContract] public class PlayMessage : MediaSessionMessage { } diff --git a/Sharpcaster/Messages/Media/SeekMessage.cs b/Sharpcaster/Messages/Media/SeekMessage.cs index 97510b8f..97fabf93 100644 --- a/Sharpcaster/Messages/Media/SeekMessage.cs +++ b/Sharpcaster/Messages/Media/SeekMessage.cs @@ -1,17 +1,16 @@ -using System.Runtime.Serialization; +using System.Text.Json.Serialization; namespace Sharpcaster.Messages.Media { /// /// Seek message /// - [DataContract] public class SeekMessage : MediaSessionMessage { /// /// Gets or sets the current time /// - [DataMember(Name = "currentTime")] + [JsonPropertyName("currentTime")] public double CurrentTime { get; set; } } } diff --git a/Sharpcaster/Messages/Media/StopMessage.cs b/Sharpcaster/Messages/Media/StopMessage.cs index e317cc14..157248bb 100644 --- a/Sharpcaster/Messages/Media/StopMessage.cs +++ b/Sharpcaster/Messages/Media/StopMessage.cs @@ -1,9 +1,10 @@ -using System.Runtime.Serialization; - -namespace Sharpcaster.Messages.Media +namespace Sharpcaster.Messages.Media { - [DataContract] - public class StopMessage : MediaSessionMessage + public class StopMediaMessage : MediaSessionMessage { + public StopMediaMessage() + { + Type = "STOP_MESSAGE"; + } } } diff --git a/Sharpcaster/Messages/Message.cs b/Sharpcaster/Messages/Message.cs index 430333e2..01376937 100644 --- a/Sharpcaster/Messages/Message.cs +++ b/Sharpcaster/Messages/Message.cs @@ -1,12 +1,11 @@ -using Sharpcaster.Interfaces; -using System.Runtime.Serialization; +using System.Text.Json.Serialization; +using Sharpcaster.Interfaces; namespace Sharpcaster.Messages { /// /// Message base class /// - [DataContract] public abstract class Message : IMessage { /// @@ -15,12 +14,12 @@ public abstract class Message : IMessage protected Message() { var type = GetType().Name; - //Get the type name without the "Message" suffix + // Get the type name without the "Message" suffix type = type.Replace("Message", ""); var firstCharacter = true; var result = ""; - //Convert the type name to uppercase with underscores - //example: "ReceiverStatusMessage" -> "RECEIVER_STATUS" + // Convert the type name to uppercase with underscores + // example: "ReceiverStatusMessage" -> "RECEIVER_STATUS" for (int i = 0; i < type.Length; i++) { var c = type[i]; @@ -44,7 +43,7 @@ protected Message() /// /// Gets or sets the message type /// - [DataMember(Name = "type")] + [JsonPropertyName("type")] public string Type { get; set; } } -} +} \ No newline at end of file diff --git a/Sharpcaster/Messages/MessageWithID.cs b/Sharpcaster/Messages/MessageWithID.cs index c1ce24e3..a7437dc5 100644 --- a/Sharpcaster/Messages/MessageWithID.cs +++ b/Sharpcaster/Messages/MessageWithID.cs @@ -1,6 +1,6 @@ using Sharpcaster.Interfaces; using System; -using System.Runtime.Serialization; +using System.Text.Json.Serialization; using System.Threading; namespace Sharpcaster.Messages @@ -8,7 +8,6 @@ namespace Sharpcaster.Messages /// /// Message with request identifier /// - [DataContract] public class MessageWithId : Message, IMessageWithId { private static int _id = new Random().Next(); @@ -16,16 +15,17 @@ public class MessageWithId : Message, IMessageWithId /// /// Gets a value indicating whether the message has a request identifier /// + [JsonIgnore] public bool HasRequestId { - get { return _requestId != null; } + get { return _requestId != 0; } } private int? _requestId; /// /// Gets or sets the request identifier /// - [DataMember(Name = "requestId")] + [JsonPropertyName("requestId")] public int RequestId { get { return (int)(_requestId ?? (_requestId = Interlocked.Increment(ref _id))); } diff --git a/Sharpcaster/Messages/MessageWithSession.cs b/Sharpcaster/Messages/MessageWithSession.cs index 4c700d1c..1dbb398b 100644 --- a/Sharpcaster/Messages/MessageWithSession.cs +++ b/Sharpcaster/Messages/MessageWithSession.cs @@ -1,14 +1,13 @@ -using System.Runtime.Serialization; +using System.Text.Json.Serialization; namespace Sharpcaster.Messages { /// /// Message with request identifier and session identifier /// - [DataContract] public class MessageWithSession : MessageWithId { - [DataMember(Name = "sessionId")] + [JsonPropertyName("sessionId")] public string SessionId { get; set; } } } diff --git a/Sharpcaster/Messages/Multizone/DeviceUpdatedMessage.cs b/Sharpcaster/Messages/Multizone/DeviceUpdatedMessage.cs index 7a6e29a1..7cf876ea 100644 --- a/Sharpcaster/Messages/Multizone/DeviceUpdatedMessage.cs +++ b/Sharpcaster/Messages/Multizone/DeviceUpdatedMessage.cs @@ -1,16 +1,15 @@ using Sharpcaster.Models.MultiZone; -using System.Runtime.Serialization; +using System.Text.Json.Serialization; namespace Sharpcaster.Messages.Multizone { /// /// Media status message /// - [DataContract] [ReceptionMessage] public class DeviceUpdatedMessage : MessageWithId { - [DataMember(Name = "device")] + [JsonPropertyName("device")] public Device Device { get; set; } } } diff --git a/Sharpcaster/Messages/Multizone/MultizoneStatusMessage.cs b/Sharpcaster/Messages/Multizone/MultizoneStatusMessage.cs index b5648a5e..ac4b174d 100644 --- a/Sharpcaster/Messages/Multizone/MultizoneStatusMessage.cs +++ b/Sharpcaster/Messages/Multizone/MultizoneStatusMessage.cs @@ -1,13 +1,11 @@ using Sharpcaster.Messages.Chromecast; using Sharpcaster.Models.MultiZone; -using System.Runtime.Serialization; namespace Sharpcaster.Messages.Multizone { /// /// Media status message /// - [DataContract] [ReceptionMessage] public class MultizoneStatusMessage : StatusMessage { diff --git a/Sharpcaster/Messages/Queue/QueueChangeMessage.cs b/Sharpcaster/Messages/Queue/QueueChangeMessage.cs index fc871c21..fcb005b4 100644 --- a/Sharpcaster/Messages/Queue/QueueChangeMessage.cs +++ b/Sharpcaster/Messages/Queue/QueueChangeMessage.cs @@ -1,17 +1,14 @@ -using System.Runtime.Serialization; +using System.Text.Json.Serialization; namespace Sharpcaster.Messages.Queue { - //TODO: not tested yet. Either implement Queue manipulation Requests or test with 2nd external client!? - - [DataContract] [ReceptionMessage] public class QueueChangeMessage : MessageWithSession { - [DataMember(Name = "changeType")] + [JsonPropertyName("changeType")] public string ChangeType { get; set; } - [DataMember(Name = "itemIds")] + [JsonPropertyName("itemIds")] public int[] ChangedIds { get; set; } } } diff --git a/Sharpcaster/Messages/Queue/QueueGetItemIdsMessage.cs b/Sharpcaster/Messages/Queue/QueueGetItemIdsMessage.cs index c21b27a3..3bac6966 100644 --- a/Sharpcaster/Messages/Queue/QueueGetItemIdsMessage.cs +++ b/Sharpcaster/Messages/Queue/QueueGetItemIdsMessage.cs @@ -1,9 +1,7 @@ using Sharpcaster.Messages.Media; -using System.Runtime.Serialization; namespace Sharpcaster.Messages.Queue { - [DataContract] public class QueueGetItemIdsMessage : MediaSessionMessage { } diff --git a/Sharpcaster/Messages/Queue/QueueGetItemsMessage.cs b/Sharpcaster/Messages/Queue/QueueGetItemsMessage.cs index 9c2e9951..9ede91db 100644 --- a/Sharpcaster/Messages/Queue/QueueGetItemsMessage.cs +++ b/Sharpcaster/Messages/Queue/QueueGetItemsMessage.cs @@ -1,12 +1,11 @@ using Sharpcaster.Messages.Media; -using System.Runtime.Serialization; +using System.Text.Json.Serialization; namespace Sharpcaster.Messages.Queue { - [DataContract] public class QueueGetItemsMessage : MediaSessionMessage { - [DataMember(Name = "itemIds")] + [JsonPropertyName("itemIds")] public int[] Ids { get; set; } } } \ No newline at end of file diff --git a/Sharpcaster/Messages/Queue/QueueItemIdsMessage.cs b/Sharpcaster/Messages/Queue/QueueItemIdsMessage.cs index 2ecefcbc..b4bcb11e 100644 --- a/Sharpcaster/Messages/Queue/QueueItemIdsMessage.cs +++ b/Sharpcaster/Messages/Queue/QueueItemIdsMessage.cs @@ -1,13 +1,12 @@ using Sharpcaster.Messages.Media; -using System.Runtime.Serialization; +using System.Text.Json.Serialization; namespace Sharpcaster.Messages.Queue { - [DataContract] [ReceptionMessage] public class QueueItemIdsMessage : MediaSessionMessage { - [DataMember(Name = "itemIds")] + [JsonPropertyName("itemIds")] public int[] Ids { get; set; } } } diff --git a/Sharpcaster/Messages/Queue/QueueItemsMessage.cs b/Sharpcaster/Messages/Queue/QueueItemsMessage.cs index ee232852..6e084c18 100644 --- a/Sharpcaster/Messages/Queue/QueueItemsMessage.cs +++ b/Sharpcaster/Messages/Queue/QueueItemsMessage.cs @@ -1,14 +1,13 @@ using Sharpcaster.Messages.Media; using Sharpcaster.Models.Queue; -using System.Runtime.Serialization; +using System.Text.Json.Serialization; namespace Sharpcaster.Messages.Queue { - [DataContract] [ReceptionMessage] public class QueueItemsMessage : MediaSessionMessage { - [DataMember(Name = "items")] + [JsonPropertyName("items")] public QueueItem[] Items { get; set; } } } diff --git a/Sharpcaster/Messages/Queue/QueueLoadMessage.cs b/Sharpcaster/Messages/Queue/QueueLoadMessage.cs index 8238f69a..da5e731e 100644 --- a/Sharpcaster/Messages/Queue/QueueLoadMessage.cs +++ b/Sharpcaster/Messages/Queue/QueueLoadMessage.cs @@ -1,15 +1,13 @@ -using Newtonsoft.Json; -using Sharpcaster.Converters; +using Sharpcaster.Converters; using Sharpcaster.Models.Media; using Sharpcaster.Models.Queue; -using System.Runtime.Serialization; +using System.Text.Json.Serialization; namespace Sharpcaster.Messages.Queue { - [DataContract] public class QueueLoadMessage : MessageWithSession { - [DataMember(Name = "items")] + [JsonPropertyName("items")] public QueueItem[] Items { get; set; } /// @@ -19,13 +17,13 @@ public class QueueLoadMessage : MessageWithSession /// This may be useful for continuation scenarios where the user was already using the sender app and in the middle decides to cast. /// In this way the sender app does not need to map between the local and remote queue positions or saves one extra QUEUE_UPDATE request. /// - [DataMember(Name = "startIndex")] - [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + [JsonPropertyName("startIndex")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public long? StartIndex { get; set; } /// /// Behavior of the queue when all items have been played. /// - [DataMember(Name = "repeatMode")] + [JsonPropertyName("repeatMode")] [JsonConverter(typeof(RepeatModeEnumConverter))] public RepeatModeType RepeatMode { get; set; } @@ -35,8 +33,8 @@ public class QueueLoadMessage : MessageWithSession /// This is to cover the common case where the user casts the item that was playing locally so the currentTime does not apply to the item permanently like the QueueItem startTime does. /// It avoids having to reset the startTime dynamically (that may not be possible if the phone has gone to sleep). /// - [DataMember(Name = "currentTime")] - [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + [JsonPropertyName("currentTime")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public long? CurrentTime { get; set; } } } diff --git a/Sharpcaster/Messages/Queue/QueueNextMessage.cs b/Sharpcaster/Messages/Queue/QueueNextMessage.cs index c9248ba1..e1725400 100644 --- a/Sharpcaster/Messages/Queue/QueueNextMessage.cs +++ b/Sharpcaster/Messages/Queue/QueueNextMessage.cs @@ -1,9 +1,7 @@ using Sharpcaster.Messages.Media; -using System.Runtime.Serialization; namespace Sharpcaster.Messages.Queue { - [DataContract] public class QueueNextMessage : MediaSessionMessage { } diff --git a/Sharpcaster/Messages/Queue/QueuePrevMessage .cs b/Sharpcaster/Messages/Queue/QueuePrevMessage .cs index e1ea7f40..8a1e086b 100644 --- a/Sharpcaster/Messages/Queue/QueuePrevMessage .cs +++ b/Sharpcaster/Messages/Queue/QueuePrevMessage .cs @@ -1,9 +1,7 @@ using Sharpcaster.Messages.Media; -using System.Runtime.Serialization; namespace Sharpcaster.Messages.Queue { - [DataContract] public class QueuePrevMessage : MediaSessionMessage { } diff --git a/Sharpcaster/Messages/Receiver/GetStatusMessage.cs b/Sharpcaster/Messages/Receiver/GetStatusMessage.cs index f3228fa4..6a233184 100644 --- a/Sharpcaster/Messages/Receiver/GetStatusMessage.cs +++ b/Sharpcaster/Messages/Receiver/GetStatusMessage.cs @@ -1,11 +1,8 @@ -using System.Runtime.Serialization; - -namespace Sharpcaster.Messages.Receiver +namespace Sharpcaster.Messages.Receiver { /// /// Get status message /// - [DataContract] public class GetStatusMessage : MessageWithId { } diff --git a/Sharpcaster/Messages/Receiver/LaunchErrorMessage.cs b/Sharpcaster/Messages/Receiver/LaunchErrorMessage.cs index 6ffffb89..41455590 100644 --- a/Sharpcaster/Messages/Receiver/LaunchErrorMessage.cs +++ b/Sharpcaster/Messages/Receiver/LaunchErrorMessage.cs @@ -1,15 +1,14 @@ -using System.Runtime.Serialization; +using System.Text.Json.Serialization; namespace Sharpcaster.Messages.Receiver { /// /// Launch Error message /// - [DataContract] [ReceptionMessage] - class LaunchErrorMessage : MessageWithId + public class LaunchErrorMessage : MessageWithId { - [DataMember(Name = "reason")] + [JsonPropertyName("reason")] public string Reason { get; set; } } } diff --git a/Sharpcaster/Messages/Receiver/LaunchMessage.cs b/Sharpcaster/Messages/Receiver/LaunchMessage.cs index d14861b9..a2b0af5f 100644 --- a/Sharpcaster/Messages/Receiver/LaunchMessage.cs +++ b/Sharpcaster/Messages/Receiver/LaunchMessage.cs @@ -1,20 +1,19 @@ -using System.Runtime.Serialization; +using System.Text.Json.Serialization; namespace Sharpcaster.Messages.Receiver { /// /// Launch message /// - [DataContract] public class LaunchMessage : MessageWithId { /// /// Gets or sets the application identifier /// - [DataMember(Name = "appId")] + [JsonPropertyName("appId")] public string ApplicationId { get; set; } - [DataMember(Name = "language")] + [JsonPropertyName("language")] public string Language { get; set; } = "en-US"; } } diff --git a/Sharpcaster/Messages/Receiver/LaunchStatusMessage.cs b/Sharpcaster/Messages/Receiver/LaunchStatusMessage.cs new file mode 100644 index 00000000..f023fe24 --- /dev/null +++ b/Sharpcaster/Messages/Receiver/LaunchStatusMessage.cs @@ -0,0 +1,13 @@ +using System.Text.Json.Serialization; + +namespace Sharpcaster.Messages.Receiver +{ + public class LaunchStatusMessage : MessageWithId + { + [JsonPropertyName("launchRequestId")] + public int LaunchRequestId { get; set; } + + [JsonPropertyName("status")] + public string Status { get; set; } + } +} diff --git a/Sharpcaster/Messages/Receiver/ReceiverStatusMessage.cs b/Sharpcaster/Messages/Receiver/ReceiverStatusMessage.cs index 9b33a61a..3b27bc8c 100644 --- a/Sharpcaster/Messages/Receiver/ReceiverStatusMessage.cs +++ b/Sharpcaster/Messages/Receiver/ReceiverStatusMessage.cs @@ -1,13 +1,11 @@ using Sharpcaster.Messages.Chromecast; using Sharpcaster.Models.ChromecastStatus; -using System.Runtime.Serialization; namespace Sharpcaster.Messages.Receiver { /// /// Receiver status message /// - [DataContract] [ReceptionMessage] public class ReceiverStatusMessage : StatusMessage { diff --git a/Sharpcaster/Messages/Receiver/SetVolumeMessage.cs b/Sharpcaster/Messages/Receiver/SetVolumeMessage.cs index 7eca6281..c54963a8 100644 --- a/Sharpcaster/Messages/Receiver/SetVolumeMessage.cs +++ b/Sharpcaster/Messages/Receiver/SetVolumeMessage.cs @@ -1,12 +1,11 @@ using Sharpcaster.Models; -using System.Runtime.Serialization; +using System.Text.Json.Serialization; namespace Sharpcaster.Messages.Receiver { - [DataContract] public class SetVolumeMessage : MessageWithId { - [DataMember(Name = "volume")] + [JsonPropertyName("volume")] public Volume Volume { get; set; } } } diff --git a/Sharpcaster/Messages/Receiver/StopMessage.cs b/Sharpcaster/Messages/Receiver/StopMessage.cs index 92e6eb7e..9e630b67 100644 --- a/Sharpcaster/Messages/Receiver/StopMessage.cs +++ b/Sharpcaster/Messages/Receiver/StopMessage.cs @@ -1,8 +1,5 @@ -using System.Runtime.Serialization; - -namespace Sharpcaster.Messages.Receiver +namespace Sharpcaster.Messages.Receiver { - [DataContract] public class StopMessage : MessageWithSession { } diff --git a/Sharpcaster/Messages/Spotify/AddUserMessage.cs b/Sharpcaster/Messages/Spotify/AddUserMessage.cs index f8c4a7c9..2bfb9cfd 100644 --- a/Sharpcaster/Messages/Spotify/AddUserMessage.cs +++ b/Sharpcaster/Messages/Spotify/AddUserMessage.cs @@ -1,11 +1,10 @@ -using System.Runtime.Serialization; +using System.Text.Json.Serialization; namespace Sharpcaster.Messages.Spotify { - [DataContract] public class AddUserMessage : MessageWithId { - [DataMember(Name = "payload")] + [JsonPropertyName("payload")] public AddUserMessagePayload Payload { get; set; } public AddUserMessage() { @@ -13,12 +12,11 @@ public AddUserMessage() } } - [DataContract] public class AddUserMessagePayload { - [DataMember(Name = "blob")] + [JsonPropertyName("blob")] public string Blob { get; set; } - [DataMember(Name = "tokenType")] + [JsonPropertyName("tokenType")] public string TokenType { get; set; } } } diff --git a/Sharpcaster/Messages/Spotify/AddUserResponseMessage.cs b/Sharpcaster/Messages/Spotify/AddUserResponseMessage.cs index f0b889c3..1d31ba78 100644 --- a/Sharpcaster/Messages/Spotify/AddUserResponseMessage.cs +++ b/Sharpcaster/Messages/Spotify/AddUserResponseMessage.cs @@ -1,8 +1,7 @@ -using System.Runtime.Serialization; +using System.Text.Json.Serialization; namespace Sharpcaster.Messages.Spotify { - [DataContract] [ReceptionMessage] public class AddUserResponseMessage : Message { @@ -11,27 +10,25 @@ public AddUserResponseMessage() Type = "addUserResponse"; } - [DataMember(Name = "payload")] + [JsonPropertyName("payload")] public AddUserResponseMessagePayload Payload { get; set; } } - [DataContract] public class AddUserResponseMessagePayload { - [DataMember(Name = "status")] + [JsonPropertyName("status")] public int Status { get; set; } - [DataMember(Name = "statusString")] + [JsonPropertyName("statusString")] public string StatusString { get; set; } - [DataMember(Name = "spotifyError")] + [JsonPropertyName("spotifyError")] public int SpotifyError { get; set; } - [DataMember(Name = "user")] + [JsonPropertyName("user")] public AddUserResponseMessagePayloadUser User { get; set; } - [DataMember(Name = "deviceId")] + [JsonPropertyName("deviceId")] public string DeviceId { get; set; } } - [DataContract] public class AddUserResponseMessagePayloadUser { - [DataMember(Name = "id")] + [JsonPropertyName("id")] public string Id { get; set; } } } diff --git a/Sharpcaster/Messages/Spotify/GetInfoMessage.cs b/Sharpcaster/Messages/Spotify/GetInfoMessage.cs index dd7d6d50..c47c898e 100644 --- a/Sharpcaster/Messages/Spotify/GetInfoMessage.cs +++ b/Sharpcaster/Messages/Spotify/GetInfoMessage.cs @@ -1,11 +1,10 @@ -using System.Runtime.Serialization; +using System.Text.Json.Serialization; namespace Sharpcaster.Messages.Spotify { - [DataContract] public class GetInfoMessage : MessageWithId { - [DataMember(Name = "payload")] + [JsonPropertyName("payload")] public GetInfoMessagePayload Payload { get; set; } public GetInfoMessage() { @@ -13,15 +12,14 @@ public GetInfoMessage() } } - [DataContract] public class GetInfoMessagePayload { - [DataMember(Name = "deviceAPI_isGroup")] + [JsonPropertyName("deviceAPI_isGroup")] public bool DeviceAPI_isGroup { get; set; } - [DataMember(Name = "deviceID")] + [JsonPropertyName("deviceID")] public string DeviceId { get; set; } - [DataMember(Name = "remoteName")] + [JsonPropertyName("remoteName")] public string RemoteName { get; set; } } } diff --git a/Sharpcaster/Messages/Spotify/GetInfoResponseMessage.cs b/Sharpcaster/Messages/Spotify/GetInfoResponseMessage.cs index 35564ec4..431fdc0f 100644 --- a/Sharpcaster/Messages/Spotify/GetInfoResponseMessage.cs +++ b/Sharpcaster/Messages/Spotify/GetInfoResponseMessage.cs @@ -1,10 +1,9 @@ using Sharpcaster.Messages.Chromecast; using Sharpcaster.Models.Spotify; -using System.Runtime.Serialization; +using System.Text.Json.Serialization; namespace Sharpcaster.Messages.Spotify { - [DataContract] [ReceptionMessage] public class GetInfoResponseMessage : StatusMessage { @@ -13,7 +12,7 @@ public GetInfoResponseMessage() Type = "getInfoResponse"; } - [DataMember(Name = "payload")] + [JsonPropertyName("payload")] public SpotifyStatus Payload { get; set; } } } diff --git a/Sharpcaster/Models/CastMessage.cs b/Sharpcaster/Models/CastMessage.cs index ff0d6059..e2bad54c 100644 --- a/Sharpcaster/Models/CastMessage.cs +++ b/Sharpcaster/Models/CastMessage.cs @@ -1,8 +1,8 @@ using Google.Protobuf; -using Newtonsoft.Json; using Sharpcaster; using System; using System.Linq; +using System.Text.Json; namespace Extensions.Api.CastChannel { @@ -43,9 +43,15 @@ public string GetJsonType() return string.Empty; } - dynamic stuff = JsonConvert.DeserializeObject(PayloadUtf8); + using (var document = JsonDocument.Parse(PayloadUtf8)) + { + if (document.RootElement.TryGetProperty("type", out JsonElement typeElement)) + { + return typeElement.GetString() ?? string.Empty; + } + } - return stuff.type; + return string.Empty; } } } diff --git a/Sharpcaster/Models/ChromecastStatus/ChromecastApplication.cs b/Sharpcaster/Models/ChromecastStatus/ChromecastApplication.cs index 97e170b3..92b372c0 100644 --- a/Sharpcaster/Models/ChromecastStatus/ChromecastApplication.cs +++ b/Sharpcaster/Models/ChromecastStatus/ChromecastApplication.cs @@ -1,21 +1,26 @@ -using Newtonsoft.Json; -using System.Collections.Generic; +using System.Collections.Generic; +using System.Text.Json.Serialization; namespace Sharpcaster.Models.ChromecastStatus { public class ChromecastApplication { - [JsonProperty("appId")] + [JsonPropertyName("appId")] public string AppId { get; set; } - [JsonProperty("displayName")] + + [JsonPropertyName("displayName")] public string DisplayName { get; set; } - [JsonProperty("namespaces")] + + [JsonPropertyName("namespaces")] public List Namespaces { get; set; } - [JsonProperty("sessionId")] + + [JsonPropertyName("sessionId")] public string SessionId { get; set; } - [JsonProperty("statusText")] + + [JsonPropertyName("statusText")] public string StatusText { get; set; } - [JsonProperty("transportId")] + + [JsonPropertyName("transportId")] public string TransportId { get; set; } } -} +} \ No newline at end of file diff --git a/Sharpcaster/Models/ChromecastStatus/ChromecastStatus.cs b/Sharpcaster/Models/ChromecastStatus/ChromecastStatus.cs index 052bdbf9..3aa70637 100644 --- a/Sharpcaster/Models/ChromecastStatus/ChromecastStatus.cs +++ b/Sharpcaster/Models/ChromecastStatus/ChromecastStatus.cs @@ -1,20 +1,24 @@ -using Newtonsoft.Json; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; +using System.Text.Json.Serialization; namespace Sharpcaster.Models.ChromecastStatus { public class ChromecastStatus { - [JsonProperty("applications")] + [JsonPropertyName("applications")] public List Applications { get; set; } - [JsonProperty("isActiveInput")] + + [JsonPropertyName("isActiveInput")] public bool IsActiveInput { get; set; } - [JsonProperty("isStandBy")] + + [JsonPropertyName("isStandBy")] public bool IsStandBy { get; set; } - [JsonProperty("volume")] + + [JsonPropertyName("volume")] public Volume Volume { get; set; } + [JsonIgnore] - public ChromecastApplication Application { get => Applications.FirstOrDefault(); } + public ChromecastApplication Application => Applications?.FirstOrDefault(); } -} +} \ No newline at end of file diff --git a/Sharpcaster/Models/ChromecastStatus/Namespace.cs b/Sharpcaster/Models/ChromecastStatus/Namespace.cs index 420f374c..3ca0a989 100644 --- a/Sharpcaster/Models/ChromecastStatus/Namespace.cs +++ b/Sharpcaster/Models/ChromecastStatus/Namespace.cs @@ -1,10 +1,10 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Sharpcaster.Models.ChromecastStatus { public class Namespace { - [JsonProperty("name")] + [JsonPropertyName("name")] public string Name { get; set; } } -} +} \ No newline at end of file diff --git a/Sharpcaster/Models/Media/Media.cs b/Sharpcaster/Models/Media/Media.cs index c83a1f48..f36f5224 100644 --- a/Sharpcaster/Models/Media/Media.cs +++ b/Sharpcaster/Models/Media/Media.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Runtime.Serialization; +using System.Text.Json.Serialization; namespace Sharpcaster.Models.Media { @@ -12,37 +13,37 @@ public class Media /// /// Gets or sets the content identifier /// - [DataMember(Name = "contentId")] + [JsonPropertyName("contentId")] public string ContentUrl { get; set; } /// /// Gets or sets the stream type /// - [DataMember(Name = "streamType")] + [JsonPropertyName("streamType")] public StreamType StreamType { get; set; } = StreamType.Buffered; /// /// Gets or sets the content type /// - [DataMember(Name = "contentType")] + [JsonPropertyName("contentType")] public string ContentType { get; set; } /// /// Gets or sets the metadata /// - [DataMember(Name = "metadata")] + [JsonPropertyName("metadata")] public MediaMetadata Metadata { get; set; } /// /// Gets or sets the duration of the media /// - [DataMember(Name = "duration")] + [JsonPropertyName("duration")] public double? Duration { get; set; } /// /// Gets or sets the custom data /// - [DataMember(Name = "customData")] + [JsonPropertyName("customData")] public IDictionary CustomData { get; set; } } } diff --git a/Sharpcaster/Models/Media/MediaStatus.cs b/Sharpcaster/Models/Media/MediaStatus.cs index 53daa521..bda9039e 100644 --- a/Sharpcaster/Models/Media/MediaStatus.cs +++ b/Sharpcaster/Models/Media/MediaStatus.cs @@ -1,88 +1,92 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; using Sharpcaster.Converters; using Sharpcaster.Models.Queue; -using System.Runtime.Serialization; namespace Sharpcaster.Models.Media { /// /// Media status /// - [DataContract] public class MediaStatus { /// /// Gets or sets the media session identifier /// - [DataMember(Name = "mediaSessionId")] + [JsonPropertyName("mediaSessionId")] public long MediaSessionId { get; set; } /// /// Gets or sets the playback rate /// - [DataMember(Name = "playbackRate")] + [JsonPropertyName("playbackRate")] public int PlaybackRate { get; set; } /// /// Gets or sets the player state /// - [DataMember(Name = "playerState")] + [JsonPropertyName("playerState")] [JsonConverter(typeof(PlayerStateEnumConverter))] public PlayerStateType PlayerState { get; set; } /// /// Gets or sets the current time /// - [DataMember(Name = "currentTime")] + [JsonPropertyName("currentTime")] public double CurrentTime { get; set; } /// /// Gets or sets the supported media commands /// - [DataMember(Name = "supportedMediaCommands")] + [JsonPropertyName("supportedMediaCommands")] public int SupportedMediaCommands { get; set; } /// /// Gets or sets the volume /// - [DataMember(Name = "volume")] + [JsonPropertyName("volume")] public Volume Volume { get; set; } /// /// Gets or sets the idle reason /// - [DataMember(Name = "idleReason")] + [JsonPropertyName("idleReason")] public string IdleReason { get; set; } /// /// Gets or sets the media /// - [DataMember(Name = "media")] + [JsonPropertyName("media")] public Media Media { get; set; } /// /// Gets or sets the current item identifier /// - [DataMember(Name = "currentItemId")] + [JsonPropertyName("currentItemId")] public int CurrentItemId { get; set; } = -1; /// /// Gets or sets the extended status /// - [DataMember(Name = "extendedStatus")] + [JsonPropertyName("extendedStatus")] public MediaStatus ExtendedStatus { get; set; } /// /// Gets or sets the repeat mode /// - [DataMember(Name = "repeatMode")] + [JsonPropertyName("repeatMode")] [JsonConverter(typeof(RepeatModeEnumConverter))] public RepeatModeType RepeatMode { get; set; } - [DataMember(Name = "queueData")] + /// + /// Gets or sets the queue data + /// + [JsonPropertyName("queueData")] public QueueData QueueData { get; set; } - [DataMember(Name = "items")] + /// + /// Gets or sets the queue items + /// + [JsonPropertyName("items")] public QueueItem[] Items { get; set; } } -} +} \ No newline at end of file diff --git a/Sharpcaster/Models/Media/PlayerStateType.cs b/Sharpcaster/Models/Media/PlayerStateType.cs index 39c8fa8a..86100ca1 100644 --- a/Sharpcaster/Models/Media/PlayerStateType.cs +++ b/Sharpcaster/Models/Media/PlayerStateType.cs @@ -5,6 +5,7 @@ public enum PlayerStateType Buffering, Idle, Paused, + Loading, Playing } } diff --git a/Sharpcaster/Models/Queue/QueueData.cs b/Sharpcaster/Models/Queue/QueueData.cs index 0e96e858..0af4c906 100644 --- a/Sharpcaster/Models/Queue/QueueData.cs +++ b/Sharpcaster/Models/Queue/QueueData.cs @@ -1,63 +1,73 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; using Sharpcaster.Converters; using Sharpcaster.Models.Media; -using System.Runtime.Serialization; namespace Sharpcaster.Models.Queue { - [DataContract] + /// + /// Represents the metadata for a media queue. + /// public class QueueData { /// /// The description of the queue. /// - [DataMember(Name = "description")] + [JsonPropertyName("description")] public string Description { get; set; } + /// /// An optional Queue entity ID, providing a Google Assistant deep link. /// - [DataMember(Name = "entity")] + [JsonPropertyName("entity")] public string Entity { get; set; } + /// /// The ID of the queue. /// - [DataMember(Name = "id")] + [JsonPropertyName("id")] public string Id { get; set; } + /// /// An Array of queue items, sorted in playback order. /// - [DataMember(Name = "items")] + [JsonPropertyName("items")] public QueueItem[] Items { get; set; } + /// /// The name of the queue. /// - [DataMember(Name = "name")] + [JsonPropertyName("name")] public string Name { get; set; } + /// /// A queue type, such as album, playlist, radio station, or tv series. /// - [DataMember(Name = "queueType")] + [JsonPropertyName("queueType")] public string QueueType { get; set; } + /// /// The continuous playback behavior of the queue. /// - [DataMember(Name = "repeatMode")] + [JsonPropertyName("repeatMode")] [JsonConverter(typeof(RepeatModeEnumConverter))] public RepeatModeType RepeatMode { get; set; } + /// /// True indicates that the queue is shuffled. /// - [DataMember(Name = "shuffle")] + [JsonPropertyName("shuffle")] public bool IsShuffle { get; set; } + /// /// The index of the item in the queue that should be used to start playback first. /// - [DataMember(Name = "startIndex")] + [JsonPropertyName("startIndex")] public long? StartIndex { get; set; } + /// /// When to start playback of the first item, expressed as the number of seconds since the beginning of the media. /// - [DataMember(Name = "startTime")] + [JsonPropertyName("startTime")] public long? StartTime { get; set; } } } \ No newline at end of file diff --git a/Sharpcaster/Models/Queue/QueueItem.cs b/Sharpcaster/Models/Queue/QueueItem.cs index 75e50597..c552c2b2 100644 --- a/Sharpcaster/Models/Queue/QueueItem.cs +++ b/Sharpcaster/Models/Queue/QueueItem.cs @@ -1,4 +1,5 @@ using System.Runtime.Serialization; +using System.Text.Json.Serialization; namespace Sharpcaster.Models.Queue { @@ -8,29 +9,34 @@ public class QueueItem /// /// Gets or sets the item identifier /// - [DataMember(Name = "itemId", EmitDefaultValue = false)] + [JsonPropertyName("itemId")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public long? ItemId { get; set; } /// /// Gets or sets the media /// - [DataMember(Name = "media")] + [JsonPropertyName("media")] public Media.Media Media { get; set; } /// /// Gets or sets a value indicating whether autoplay is enabled or not /// - [DataMember(Name = "autoPlay")] + [JsonPropertyName("autoPlay")] public bool? IsAutoPlay { get; set; } - [DataMember(Name = "orderId", EmitDefaultValue = false)] + [JsonPropertyName("orderId")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public long? OrderId { get; set; } - [DataMember(Name = "startTime", EmitDefaultValue = false)] + [JsonPropertyName("startTime")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public long? StartTime { get; set; } - [DataMember(Name = "preloadTime", EmitDefaultValue = false)] + [JsonPropertyName("preloadTime")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public long? PreloadTime { get; set; } - [DataMember(Name = "activeTrackIds", EmitDefaultValue = false)] + [JsonPropertyName("activeTrackIds")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public long[] ActiveTrackIds { get; set; } } } diff --git a/Sharpcaster/Models/SharpCasterTaskCompletionSource.cs b/Sharpcaster/Models/SharpCasterTaskCompletionSource.cs new file mode 100644 index 00000000..9e340ff6 --- /dev/null +++ b/Sharpcaster/Models/SharpCasterTaskCompletionSource.cs @@ -0,0 +1,33 @@ +using System; +using System.Threading.Tasks; + +namespace Sharpcaster.Models +{ + public class SharpCasterTaskCompletionSource + { + private readonly TaskCompletionSource _tcs; + + public Task Task { get => _tcs.Task; } + + public SharpCasterTaskCompletionSource() + { + _tcs = new TaskCompletionSource(); + } + + public void SetResult(string parameter) + { + _tcs.SetResult(parameter); + } + + public void SetException(Exception exception) + { + _tcs.SetException(exception); + } + } + + public enum TaskCompletionMethod + { + SetResult, + SetException + } +} diff --git a/Sharpcaster/Models/Volume.cs b/Sharpcaster/Models/Volume.cs index 72dc35ca..aafb7fde 100644 --- a/Sharpcaster/Models/Volume.cs +++ b/Sharpcaster/Models/Volume.cs @@ -1,14 +1,13 @@ -using System.Runtime.Serialization; +using System.Text.Json.Serialization; namespace Sharpcaster.Models { - [DataContract] public class Volume { - [DataMember(Name = "level")] + [JsonPropertyName("level")] public double? Level { get; set; } - [DataMember(Name = "muted")] + [JsonPropertyName("muted")] public bool? Muted { get; set; } } } diff --git a/Sharpcaster/Sharpcaster.csproj b/Sharpcaster/Sharpcaster.csproj index a7c1e55a..7c59fda0 100644 --- a/Sharpcaster/Sharpcaster.csproj +++ b/Sharpcaster/Sharpcaster.csproj @@ -1,7 +1,7 @@  - netstandard2.0 + net9.0;netstandard2.0 Sharpcaster git https://github.com/Tapanila/SharpCaster/ @@ -15,8 +15,13 @@ MIT sharpcaster-logo-64x64.png README.md + true + + 9.0 + + true @@ -29,15 +34,15 @@ - + - - + all runtime; build; native; contentfiles; analyzers; buildtransitive +