SharpCaster is a cross-platform toolkit for communicating with Google Chromecast devices. It includes:
- C# SDK (NuGet): A library for .NET apps to discover, connect, launch apps, and control media on Chromecast devices.
- Sharpcaster Console (CLI): A cross‑platform command-line app for controlling Chromecast from your terminal, distributed via Chocolatey and Homebrew.
C# SDK supports .NET Standard 2.0 and .NET 9 (including Native AOT). Sharpcaster console is built with Native AOT for fast startup, low memory usage and doesn't require .NET installed, works on Windows, macOS, and Linux.
- Features
- Installation
- Quick Start
- Comprehensive Examples
- Media Queue Management
- Volume Control
- Event Handling
- Custom Chromecast Channels
- Troubleshooting
- Contributing
- License
âś… Device Discovery: Automatic discovery of Chromecast devices on your local network using mDNS
âś… Media Control: Complete media playback control (play, pause, stop, seek, volume)
âś… Queue Management: Support for media queues with navigation (next, previous, shuffle, repeat)
âś… Application Management: Launch and manage Chromecast applications
âś… Custom Channels: Extensible architecture for custom Chromecast channels
âś… Cross-Platform: Compatible with .NET Standard 2.0 and .NET 9
âś… AOT Ready: Full support for Native AOT compilation in .NET 9
âś… Async/Await: Modern async programming model throughout
âś… Event-Driven: Rich event system for real-time status updates
Install via NuGet Package Manager:
Install-Package SharpCasterOr via .NET CLI:
dotnet add package SharpCasterControl Chromecast devices from your terminal.
- Homebrew (macOS/Linux):
brew tap Tapanila/sharpcaster brew install sharpcaster- Chocolatey (Windows):
choco install sharpcaster --preAfter installation, run sharpcaster for interactive mode, or use direct commands like:
sharpcaster list sharpcaster "Living Room TV" play "https://example.com/video.mp4" --title "Sample"For full CLI usage and examples, see SharpCaster.Console/README.md.
using Sharpcaster; using Sharpcaster.Models; using Sharpcaster.Models.Media; // Discover Chromecast devices var locator = new ChromecastLocator(); var cancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(5)).Token; var chromecasts = await locator.FindReceiversAsync(cancellationToken); if (!chromecasts.Any()) { Console.WriteLine("No Chromecast devices found"); return; } // Connect to first found device var chromecast = chromecasts.First(); var client = new ChromecastClient(); await client.ConnectChromecast(chromecast); Console.WriteLine($"Connected to {chromecast.Name}");// Launch the default media receiver app await client.LaunchApplicationAsync("CC1AD845"); // Default Media Receiver // Create and load media var media = new Media { ContentUrl = "https://commondatastorage.googleapis.com/gtv-videos-bucket/CastVideos/mp4/DesigningForGoogleCast.mp4", ContentType = "video/mp4", Metadata = new MediaMetadata { Title = "Sample Video", SubTitle = "A demonstration video" } }; var mediaStatus = await client.MediaChannel.LoadAsync(media); Console.WriteLine($"Media loaded: {mediaStatus.PlayerState}");public class ChromecastMediaPlayer { private ChromecastClient _client; private ChromecastReceiver _device; public async Task<bool> ConnectAsync(string deviceName = null) { try { var locator = new ChromecastLocator(); var devices = await locator.FindReceiversAsync(CancellationToken.None); _device = deviceName != null ? devices.FirstOrDefault(d => d.Name.Contains(deviceName)) : devices.FirstOrDefault(); if (_device == null) return false; _client = new ChromecastClient(); await _client.ConnectChromecast(_device); // Subscribe to events _client.MediaChannel.StatusChanged += OnMediaStatusChanged; _client.Disconnected += OnDisconnected; return true; } catch (Exception ex) { Console.WriteLine($"Connection failed: {ex.Message}"); return false; } } public async Task PlayVideoAsync(string url, string title = null) { await _client.LaunchApplicationAsync("CC1AD845"); // Default Media Receiver var media = new Media { ContentUrl = url, ContentType = GetContentType(url), Metadata = new MediaMetadata { Title = title ?? Path.GetFileNameWithoutExtension(url), MetadataType = MetadataType.Movie } }; await _client.MediaChannel.LoadAsync(media); } public async Task PlayAudioAsync(string url, string title = null, string artist = null) { await _client.LaunchApplicationAsync("CC1AD845"); var media = new Media { ContentUrl = url, ContentType = GetContentType(url), Metadata = new MusicTrackMetadata { Title = title ?? Path.GetFileNameWithoutExtension(url), Artist = artist, MetadataType = MetadataType.MusicTrack } }; await _client.MediaChannel.LoadAsync(media); } private void OnMediaStatusChanged(object sender, MediaStatus status) { Console.WriteLine($"Media Status: {status.PlayerState} - {status.CurrentTime:F1}s"); } private void OnDisconnected(object sender, EventArgs e) { Console.WriteLine("Disconnected from Chromecast"); } private static string GetContentType(string url) { var extension = Path.GetExtension(url).ToLower(); return extension switch { ".mp4" => "video/mp4", ".mp3" => "audio/mpeg", ".wav" => "audio/wav", ".webm" => "video/webm", _ => "video/mp4" }; } }SharpCaster supports advanced queue operations for playlist-style media playback:
// Create a media queue var queueItems = new[] { new QueueItem { Media = new Media { ContentUrl = "https://example.com/song1.mp3", ContentType = "audio/mpeg", Metadata = new MusicTrackMetadata { Title = "Song 1", Artist = "Artist 1" } } }, new QueueItem { Media = new Media { ContentUrl = "https://example.com/song2.mp3", ContentType = "audio/mpeg", Metadata = new MusicTrackMetadata { Title = "Song 2", Artist = "Artist 2" } } } }; // Load queue with repeat mode await client.MediaChannel.QueueLoadAsync(queueItems, 0, RepeatModeType.ALL); // Navigate through queue await client.MediaChannel.QueueNextAsync(); // Next track await client.MediaChannel.QueuePrevAsync(); // Previous track // Get queue information var itemIds = await client.MediaChannel.QueueGetItemIdsAsync(); var items = await client.MediaChannel.QueueGetItemsAsync(itemIds);// Get current volume var status = await client.ReceiverChannel.GetChromecastStatusAsync(); Console.WriteLine($"Current volume: {status.Volume.Level:P0}"); // Set volume (0.0 to 1.0) await client.ReceiverChannel.SetVolumeAsync(0.5f); // Mute/unmute await client.ReceiverChannel.SetMutedAsync(true); await client.ReceiverChannel.SetMutedAsync(false);SharpCaster provides rich event support for real-time updates:
// Media events client.MediaChannel.StatusChanged += (sender, status) => { Console.WriteLine($"Player State: {status.PlayerState}"); Console.WriteLine($"Current Time: {status.CurrentTime}s"); Console.WriteLine($"Duration: {status.Media?.Duration}s"); }; // Connection events client.Disconnected += (sender, args) => { Console.WriteLine("Connection lost to Chromecast"); }; // Application events client.ReceiverChannel.StatusChanged += (sender, status) => { foreach (var app in status.Applications ?? []) { Console.WriteLine($"Running app: {app.DisplayName} ({app.AppId})"); } };Create custom channels for specialized applications:
public class CustomGameChannel : ChromecastChannel { public CustomGameChannel(ILogger<CustomGameChannel> logger) : base("custom.game", logger) { } public async Task SendGameCommandAsync(string command, object data) { var message = new { type = command, data = data }; await SendAsync(JsonSerializer.Serialize(message)); } protected override async Task OnMessageReceivedAsync(string message, string messageType) { // Handle custom game messages var gameMessage = JsonSerializer.Deserialize<GameMessage>(message); // Process game-specific logic } } // Register and use custom channel var gameChannel = new CustomGameChannel(logger); client.RegisterChannel(gameChannel);You can also reverse engineer existing channels:
- In Chrome, go to
chrome://net-export/ - Select 'Include raw bytes (will include cookies and credentials)'
- Click 'Start Logging to Disk'
- Cast from your favorite web app
- Stop logging and open the log in netlog-viewer
- Search for
type:SOCKETand find familiar JSON data - Collect the exchanged JSON
- Create a new class inheriting from
ChromecastChanneland implement your logic
Device Discovery Issues:
// Increase discovery timeout for slow networks var cancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(10)).Token; var devices = await locator.FindReceiversAsync(cancellationToken); // Manually specify device if discovery fails var manualDevice = new ChromecastReceiver { Name = "My Chromecast", DeviceUri = new Uri("http://192.168.1.100"), Port = 8009 };Connection Timeouts:
// Enable logging for debugging var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole().SetMinimumLevel(LogLevel.Debug)); var client = new ChromecastClient(loggerFactory);Media Loading Failures:
- Ensure media URLs are publicly accessible
- Verify correct
ContentTypeis specified - Check that the Chromecast supports the media format
- Use HTTPS URLs when possible
Network Issues:
- Ensure device is on the same network as Chromecast
- Check firewall settings (port 8009 must be accessible)
- Verify mDNS/Bonjour is enabled on the network
try { await client.MediaChannel.LoadAsync(media); } catch (TimeoutException) { Console.WriteLine("Request timed out - check network connection"); } catch (InvalidOperationException ex) { Console.WriteLine($"Invalid operation: {ex.Message}"); } catch (Exception ex) { Console.WriteLine($"Unexpected error: {ex.Message}"); }- .NET Standard 2.0 - Maximum compatibility across .NET implementations
- .NET 9 - Latest features including Native AOT support
- Compatible with: .NET Framework 4.6.1+, .NET Core 2.0+, .NET 5+, Xamarin, Unity
We welcome contributions! Here's how you can help:
- Fork the repository
- Create a feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
git clone https://github.com/Tapanila/SharpCaster.git cd SharpCaster dotnet restore dotnet build dotnet testTests require a physical Chromecast device on the network:
dotnet testSome of the tests may require few tries to pass due to network conditions.
This project is licensed under the MIT License - see the LICENSE file for details.

