Skip to content

Commit 976c316

Browse files
authored
Dotnet 9 version and AOT (#306)
There has been quite a lot of changes on this version. There's multiple breaking changes on the logic, calls and etc. - Now producing dotnet9 and dotnetstandard 2.0 packages. Before there was only dotnetstandard 2.0 - [AOT Compilation](https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot). Dotnet9 version is AOT compatible - Switched from newtonsoft json to system.text.json - Changed the event types for channels that are returning status to make them easier to use - Removed reflection usage - Performance improvements and memory usage reduction - Improved error handling
1 parent 1138a92 commit 976c316

File tree

94 files changed

+1079
-695
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

94 files changed

+1079
-695
lines changed

.github/workflows/codeql-analysis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ jobs:
4848
- name: Setup .NET
4949
uses: actions/setup-dotnet@v4
5050
with:
51-
dotnet-version: 8.0.x
51+
dotnet-version: 9.0.x
5252
- name: Install dependencies
5353
run: dotnet restore
5454
- name: Build

.github/workflows/dotnet.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
- name: Setup .NET
2020
uses: actions/setup-dotnet@v4
2121
with:
22-
dotnet-version: 8.0.x
22+
dotnet-version: 9.0.x
2323
- name: Restore dependencies
2424
run: dotnet restore SharpCaster.sln
2525
- name: tag

README.md

Lines changed: 59 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,80 @@
1-
![Icon](https://raw.githubusercontent.com/Tapanila/SharpCaster/master/Assets/sharpcaster-logo-64x64.png)
1+
![SharpCaster Logo](https://raw.githubusercontent.com/Tapanila/SharpCaster/master/Assets/sharpcaster-logo-64x64.png)
2+
23
# SharpCaster
34

4-
### Currently Supported Platforms
5-
* [.NET Standard 2.0](https://docs.microsoft.com/en-us/dotnet/standard/net-standard?tabs=net-standard-2-0)
5+
[![.NET Build Status](https://github.com/Tapanila/SharpCaster/actions/workflows/dotnet.yml/badge.svg)](https://github.com/Tapanila/SharpCaster/actions/workflows/dotnet.yml)
6+
[![NuGet Status](http://img.shields.io/nuget/v/SharpCaster.svg?style=flat)](https://www.nuget.org/packages/SharpCaster/)
7+
8+
SharpCaster is a cross-platform C# SDK for communicating with Google Chromecast devices. It enables .NET applications to discover, connect, launch apps, and control media playback on Chromecast devices.
9+
10+
---
611

7-
[![.NET](https://github.com/Tapanila/SharpCaster/actions/workflows/dotnet.yml/badge.svg)](https://github.com/Tapanila/SharpCaster/actions/workflows/dotnet.yml)
12+
## Features
13+
- Discover Chromecast devices on your local network
14+
- Connect and launch applications on Chromecast
15+
- Load and control media playback (play, pause, stop, etc.)
16+
- Support for custom Chromecast channels
17+
- Compatible with .NET Standard 2.0 and .NET 9
818

9-
SharpCaster is Chromecast C# SDK any platform support .net standard 2.0.
19+
---
1020

11-
## The nuget package [![NuGet Status](http://img.shields.io/nuget/v/SharpCaster.svg?style=flat)](https://www.nuget.org/packages/SharpCaster/)
21+
## Supported Platforms
22+
- [.NET Standard 2.0](https://docs.microsoft.com/en-us/dotnet/standard/net-standard?tabs=net-standard-2-0)
23+
- [.NET 9](https://dotnet.microsoft.com/en-us/download/dotnet/9.0)
1224

13-
https://nuget.org/packages/SharpCaster/
25+
---
1426

15-
PM> Install-Package SharpCaster
27+
## Installation
28+
Install the NuGet package:
29+
PM> Install-Package SharpCaster
30+
Or via .NET CLI:
31+
dotnet add package SharpCaster
32+
[NuGet Gallery](https://nuget.org/packages/SharpCaster/)
1633

17-
# Getting started
34+
---
1835

19-
## Finding chromecast devices from network
20-
```cs
21-
IChromecastLocator locator = new MdnsChromecastLocator();
36+
## Getting Started
37+
38+
### 1. Discover Chromecast DevicesIChromecastLocator locator = new MdnsChromecastLocator();
2239
var source = new CancellationTokenSource(TimeSpan.FromMilliseconds(1500));
2340
var chromecasts = await locator.FindReceiversAsync(source.Token);
24-
```
25-
## Connecting to chromecast device, launch application and load media
26-
```cs
27-
var chromecast = chromecasts.First();
41+
### 2. Connect, Launch App, and Load Mediavar chromecast = chromecasts.First();
2842
var client = new ChromecastClient();
2943
await client.ConnectChromecast(chromecast);
30-
_ = await client.LaunchApplicationAsync("B3419EF5");
44+
await client.LaunchApplicationAsync("B3419EF5"); // Replace with your app ID
3145

3246
var media = new Media
3347
{
34-
ContentUrl = "https://commondatastorage.googleapis.com/gtv-videos-bucket/CastVideos/mp4/DesigningForGoogleCast.mp4"
48+
ContentUrl = "https://commondatastorage.googleapis.com/gtv-videos-bucket/CastVideos/mp4/DesigningForGoogleCast.mp4"
3549
};
36-
_ = await client.MediaChannel.LoadAsync(media);
37-
```
50+
await client.MediaChannel.LoadAsync(media);
51+
---
3852

39-
## SharpCaster Demo
53+
## Demo
4054

4155
![SharpCaster Simple demo](https://raw.githubusercontent.com/tapanila/SharpCaster/master/Assets/SharpCaster.Simple.Demo.gif)
4256

43-
## Adding support for custom chromecast channels
44-
45-
* In Chrome, go to `chrome://net-export/`
46-
* Select 'Include raw bytes (will include cookies and credentials)'
47-
* Click 'Start Logging to Disk'
48-
* Open a new tab, browse to your favorite application on the web that has Chromecast support and start casting.
49-
* Go back to the tab that is capturing events and click on stop.
50-
* Open https://netlog-viewer.appspot.com/ and select your event log file.
51-
* Browse to https://netlog-viewer.appspot.com/#events&q=type:SOCKET, and find the socket that has familiar JSON data.
52-
* Go through the results and collect the JSON that is exchanged.
53-
* Now you can create a new class that inherits from ChromecastChannel and implement the logic to send and receive messages.
57+
---
58+
59+
## Custom Chromecast Channels
60+
61+
You can add support for custom Chromecast channels by reverse engineering the communication:
62+
63+
1. In Chrome, go to `chrome://net-export/`
64+
2. Select 'Include raw bytes (will include cookies and credentials)'
65+
3. Click 'Start Logging to Disk'
66+
4. Cast from your favorite web app
67+
5. Stop logging and open the log in [netlog-viewer](https://netlog-viewer.appspot.com/)
68+
6. Search for `type:SOCKET` and find familiar JSON data
69+
7. Collect the exchanged JSON
70+
8. Create a new class inheriting from `ChromecastChannel` and implement your logic
71+
72+
---
73+
74+
## Contributing
75+
Contributions, issues, and feature requests are welcome! Feel free to open an issue or submit a pull request.
76+
77+
---
78+
79+
## License
80+
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.

SharpCaster.sln

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sharpcaster", "Sharpcaster\
77
EndProject
88
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sharpcaster.Test", "Sharpcaster.Test\Sharpcaster.Test.csproj", "{C8C0A3A8-C6AC-4E1B-8D3B-E6764C45C35B}"
99
EndProject
10+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sharpcaster.Test.Aot", "Sharpcaster.Test.Aot\Sharpcaster.Test.Aot.csproj", "{AA4C0985-3C97-496E-AF38-ADD0DC285A4C}"
11+
EndProject
1012
Global
1113
GlobalSection(SolutionConfigurationPlatforms) = preSolution
1214
Debug|Any CPU = Debug|Any CPU
@@ -21,6 +23,10 @@ Global
2123
{C8C0A3A8-C6AC-4E1B-8D3B-E6764C45C35B}.Debug|Any CPU.Build.0 = Debug|Any CPU
2224
{C8C0A3A8-C6AC-4E1B-8D3B-E6764C45C35B}.Release|Any CPU.ActiveCfg = Release|Any CPU
2325
{C8C0A3A8-C6AC-4E1B-8D3B-E6764C45C35B}.Release|Any CPU.Build.0 = Release|Any CPU
26+
{AA4C0985-3C97-496E-AF38-ADD0DC285A4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
27+
{AA4C0985-3C97-496E-AF38-ADD0DC285A4C}.Debug|Any CPU.Build.0 = Debug|Any CPU
28+
{AA4C0985-3C97-496E-AF38-ADD0DC285A4C}.Release|Any CPU.ActiveCfg = Release|Any CPU
29+
{AA4C0985-3C97-496E-AF38-ADD0DC285A4C}.Release|Any CPU.Build.0 = Release|Any CPU
2430
EndGlobalSection
2531
GlobalSection(SolutionProperties) = preSolution
2632
HideSolutionNode = FALSE
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
using System;
2+
using System.Linq;
3+
using System.Threading.Tasks;
4+
using Sharpcaster.Models;
5+
using Sharpcaster;
6+
7+
namespace Sharpcaster.Test.Aot
8+
{
9+
[TestClass]
10+
public class ChromecastApplicationTester
11+
{
12+
[TestMethod]
13+
public async Task ConnectLaunchDisconnect()
14+
{
15+
// Test 1: Discover at least one Chromecast device
16+
var locator = new MdnsChromecastLocator();
17+
var receivers = await locator.FindReceiversAsync();
18+
if (receivers == null || receivers.Count() == 0)
19+
{
20+
throw new Exception("No Chromecast devices found on the network.");
21+
}
22+
23+
// Test 2: Connect to the first Chromecast and launch an app
24+
var receiver = receivers.First();
25+
var client = new ChromecastClient();
26+
await client.ConnectChromecast(receiver);
27+
var status = await client.LaunchApplicationAsync("B3419EF5");
28+
if (status.Application?.AppId != "B3419EF5")
29+
{
30+
throw new Exception("Failed to launch application on Chromecast.");
31+
}
32+
33+
// Test 3: Disconnect
34+
await client.DisconnectAsync();
35+
}
36+
}
37+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using Sharpcaster.Extensions;
2+
using Sharpcaster.Interfaces;
3+
using Sharpcaster.Messages.Connection;
4+
using Sharpcaster.Messages.Heartbeat;
5+
using System.Text.Json;
6+
7+
namespace Sharpcaster.Test.Aot
8+
{
9+
[TestClass]
10+
public sealed class JsonTesting
11+
{
12+
[TestMethod]
13+
public void TestConnectMessageSerialization()
14+
{
15+
IMessage connectMessage = new ConnectMessage();
16+
var requestId = ((IMessageWithId)connectMessage).RequestId;
17+
18+
var output = JsonSerializer.Serialize(connectMessage, SharpcasteSerializationContext.Default.ConnectMessage);
19+
Assert.AreEqual("{\"requestId\":" + requestId + ",\"type\":\"CONNECT\"}", output);
20+
}
21+
22+
[TestMethod]
23+
public void TestPingMessageSerialization()
24+
{
25+
IMessage pingMessage = new PingMessage();
26+
27+
var output = JsonSerializer.Serialize(pingMessage, SharpcasteSerializationContext.Default.PingMessage);
28+
Assert.AreEqual("{\"type\":\"PING\"}", output);
29+
}
30+
31+
[TestMethod]
32+
public void TestPingMessageDeserialization()
33+
{
34+
const string input = "{\"type\":\"PING\"}";
35+
var message = JsonSerializer.Deserialize(input, SharpcasteSerializationContext.Default.PingMessage);
36+
37+
Assert.AreEqual("PING", message?.Type);
38+
}
39+
}
40+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)]
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<Project Sdk="MSTest.Sdk/3.6.1">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net9.0-windows</TargetFramework>
5+
<LangVersion>latest</LangVersion>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
<publishAoT>true</publishAoT>
9+
<!--
10+
Displays error on console in addition to the log file. Note that this feature comes with a performance impact.
11+
For more information, visit https://learn.microsoft.com/dotnet/core/testing/unit-testing-platform-integration-dotnet-test#show-failure-per-test
12+
-->
13+
<TestingPlatformShowTestsFailure>true</TestingPlatformShowTestsFailure>
14+
</PropertyGroup>
15+
16+
<ItemGroup>
17+
<ProjectReference Include="..\Sharpcaster\Sharpcaster.csproj" />
18+
</ItemGroup>
19+
20+
<ItemGroup>
21+
<PackageReference Update="Microsoft.CodeCoverage.MSBuild" Version="17.14.2" />
22+
</ItemGroup>
23+
24+
<ItemGroup>
25+
<PackageReference Update="Microsoft.Testing.Extensions.CodeCoverage" Version="17.14.2" />
26+
</ItemGroup>
27+
28+
<ItemGroup>
29+
<PackageReference Update="Microsoft.Testing.Extensions.TrxReport" Version="1.7.3" />
30+
</ItemGroup>
31+
32+
<ItemGroup>
33+
<PackageReference Update="Microsoft.Testing.Platform.MSBuild" Version="1.7.3" />
34+
</ItemGroup>
35+
36+
<ItemGroup>
37+
<PackageReference Update="MSTest.TestFramework" Version="3.9.3" />
38+
</ItemGroup>
39+
40+
</Project>

Sharpcaster.Test/ChromecastApplicationTester.cs

Lines changed: 10 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using Sharpcaster.Models;
22
using Sharpcaster.Test.customChannel;
3+
using Sharpcaster.Test.customMessage;
34
using Sharpcaster.Test.helper;
5+
using System.Text.Json;
46
using System.Threading.Tasks;
57
using Xunit;
68
using Xunit.Abstractions;
@@ -18,7 +20,7 @@ public ChromecastApplicationTester(ITestOutputHelper outputHelper, ChromecastDev
1820
}
1921

2022
[Theory]
21-
[MemberData(nameof(ChromecastReceiversFilter.GetAll), MemberType = typeof(ChromecastReceiversFilter))]
23+
[MemberData(nameof(ChromecastReceiversFilter.GetChromecastUltra), MemberType = typeof(ChromecastReceiversFilter))]
2224
public async Task ConnectToChromecastAndLaunchApplication(ChromecastReceiver receiver)
2325
{
2426
var TestHelper = new TestHelper();
@@ -31,7 +33,7 @@ public async Task ConnectToChromecastAndLaunchApplication(ChromecastReceiver rec
3133
}
3234

3335
[Theory]
34-
[MemberData(nameof(ChromecastReceiversFilter.GetAll), MemberType = typeof(ChromecastReceiversFilter))]
36+
[MemberData(nameof(ChromecastReceiversFilter.GetChromecastUltra), MemberType = typeof(ChromecastReceiversFilter))]
3537
public async Task ConnectToChromecastAndLaunchApplicationTwice(ChromecastReceiver receiver)
3638
{
3739
var TestHelper = new TestHelper();
@@ -47,26 +49,8 @@ public async Task ConnectToChromecastAndLaunchApplicationTwice(ChromecastReceive
4749
Assert.Equal(firstLaunchTransportId, status.Application.TransportId);
4850
}
4951

50-
[Theory(Skip = "This does not pass any more. Now my JBL reacts as the other device - not changing the Transport ID !?")]
51-
[MemberData(nameof(ChromecastReceiversFilter.GetJblSpeaker), MemberType = typeof(ChromecastReceiversFilter))]
52-
public async Task ConnectToChromecastAndLaunchApplicationTwiceWithoutJoining1(ChromecastReceiver receiver)
53-
{
54-
var TestHelper = new TestHelper();
55-
var client = await TestHelper.CreateAndConnectClient(output, receiver);
56-
var status = await client.LaunchApplicationAsync("B3419EF5");
57-
58-
var firstLaunchTransportId = status.Application.TransportId;
59-
await client.DisconnectAsync();
60-
61-
_ = await client.ConnectChromecast(receiver);
62-
status = await client.LaunchApplicationAsync("B3419EF5", false);
63-
64-
// My JBL Device (almost every time - but not always ) makes a new ID here!!!! (The other device - ChromecastAudio DOES NOT!?)
65-
Assert.NotEqual(firstLaunchTransportId, status.Application.TransportId);
66-
}
67-
6852
[Theory]
69-
[MemberData(nameof(ChromecastReceiversFilter.GetDefaultDevice), MemberType = typeof(ChromecastReceiversFilter))]
53+
[MemberData(nameof(ChromecastReceiversFilter.GetAny), MemberType = typeof(ChromecastReceiversFilter))]
7054
public async Task ConnectToChromecastAndLaunchApplicationTwiceWithoutJoining2(ChromecastReceiver receiver)
7155
{
7256
var TestHelper = new TestHelper();
@@ -84,7 +68,7 @@ public async Task ConnectToChromecastAndLaunchApplicationTwiceWithoutJoining2(Ch
8468
}
8569

8670
[Theory]
87-
[MemberData(nameof(ChromecastReceiversFilter.GetAll), MemberType = typeof(ChromecastReceiversFilter))]
71+
[MemberData(nameof(ChromecastReceiversFilter.GetChromecastUltra), MemberType = typeof(ChromecastReceiversFilter))]
8872
public async Task ConnectToChromecastAndLaunchApplicationAThenLaunchApplicationB(ChromecastReceiver receiver)
8973
{
9074
var TestHelper = new TestHelper();
@@ -101,7 +85,7 @@ public async Task ConnectToChromecastAndLaunchApplicationAThenLaunchApplicationB
10185
}
10286

10387
[Theory]
104-
[MemberData(nameof(ChromecastReceiversFilter.GetAll), MemberType = typeof(ChromecastReceiversFilter))]
88+
[MemberData(nameof(ChromecastReceiversFilter.GetAny), MemberType = typeof(ChromecastReceiversFilter))]
10589
public async Task ConnectToChromecastAndLaunchApplicationOnceAndJoinIt(ChromecastReceiver receiver)
10690
{
10791
var TestHelper = new TestHelper();
@@ -129,7 +113,9 @@ public async Task ConnectToChromecastAndLaunchWebPage(ChromecastReceiver receive
129113
SessionId = client.GetChromecastStatus().Application.SessionId
130114
};
131115

132-
await client.SendAsync(null, "urn:x-cast:com.boombatower.chromecast-dashboard", req, client.GetChromecastStatus().Application.SessionId);
116+
var requestPayload = JsonSerializer.Serialize(req, WebMessageSerializationContext.Default.WebMessage);
117+
118+
await client.SendAsync(null, "urn:x-cast:com.boombatower.chromecast-dashboard", requestPayload, client.GetChromecastStatus().Application.SessionId);
133119
await Task.Delay(5000);
134120
}
135121
}

Sharpcaster.Test/ChromecastConnectionTester.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
using Xunit.Abstractions;
66
using Sharpcaster.Test.helper;
77
using Sharpcaster.Models.Media;
8-
using System;
98
using System.Linq;
9+
using System.Collections.Generic;
1010

1111
namespace Sharpcaster.Test
1212
{
@@ -22,7 +22,7 @@ public ChromecastConnectionTester(ITestOutputHelper outputHelper, ChromecastDevi
2222
}
2323

2424
[Theory]
25-
[MemberData(nameof(ChromecastReceiversFilter.GetAll), MemberType = typeof(ChromecastReceiversFilter))]
25+
[MemberData(nameof(ChromecastReceiversFilter.GetAny), MemberType = typeof(ChromecastReceiversFilter))]
2626
public async Task SearchChromecastsAndConnectToIt(ChromecastReceiver receiver)
2727
{
2828
var TestHelper = new TestHelper();
@@ -76,7 +76,7 @@ public async Task TestingHeartBeat(ChromecastReceiver receiver)
7676
int commandsToRun = 10;
7777

7878
//We are setting up an event to listen to status change. Because we don't know when the video has started to play
79-
client.MediaChannel.StatusChanged += (object sender, EventArgs e) =>
79+
client.MediaChannel.StatusChanged += (object sender, MediaStatus e) =>
8080
{
8181
_autoResetEvent.Set();
8282
};

0 commit comments

Comments
 (0)