{"id":15011606,"url":"https://github.com/tapanila/sharpcaster","last_synced_at":"2025-10-09T10:09:59.831Z","repository":{"id":39622406,"uuid":"49721827","full_name":"Tapanila/SharpCaster","owner":"Tapanila","description":" Chromecast C# SDK for .net standard 2.0","archived":false,"fork":false,"pushed_at":"2025-02-10T15:14:54.000Z","size":1098,"stargazers_count":315,"open_issues_count":6,"forks_count":50,"subscribers_count":23,"default_branch":"main","last_synced_at":"2025-05-14T23:06:19.554Z","etag":null,"topics":["android-platform","chromecast","chromecast-audio","chromecast-device","chromecast-receiver","chromecast-sender","csharp","csharp-library","nuget","nuget-package","sdk","sharpcaster","uwp","windows-phone","xamarin"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Tapanila.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2016-01-15T13:59:36.000Z","updated_at":"2025-03-29T23:33:00.000Z","dependencies_parsed_at":"2024-04-29T16:52:29.689Z","dependency_job_id":"4546517e-2f9b-4b0b-82f5-32e4c8da13af","html_url":"https://github.com/Tapanila/SharpCaster","commit_stats":{"total_commits":351,"total_committers":9,"mean_commits":39.0,"dds":0.4245014245014245,"last_synced_commit":"fdf5c6d90a934db7c77f175584422b871eb891ba"},"previous_names":[],"tags_count":37,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Tapanila%2FSharpCaster","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Tapanila%2FSharpCaster/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Tapanila%2FSharpCaster/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Tapanila%2FSharpCaster/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Tapanila","download_url":"https://codeload.github.com/Tapanila/SharpCaster/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254243362,"owners_count":22038046,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android-platform","chromecast","chromecast-audio","chromecast-device","chromecast-receiver","chromecast-sender","csharp","csharp-library","nuget","nuget-package","sdk","sharpcaster","uwp","windows-phone","xamarin"],"created_at":"2024-09-24T19:41:20.014Z","updated_at":"2025-10-09T10:09:59.826Z","avatar_url":"https://github.com/Tapanila.png","language":"C#","readme":"![SharpCaster Logo](https://raw.githubusercontent.com/Tapanila/SharpCaster/master/Assets/sharpcaster-logo-64x64.png)\n\n# SharpCaster\n\n[![.NET Build Status](https://github.com/Tapanila/SharpCaster/actions/workflows/dotnet.yml/badge.svg)](https://github.com/Tapanila/SharpCaster/actions/workflows/dotnet.yml)\n[![NuGet Status](http://img.shields.io/nuget/v/SharpCaster.svg?style=flat)](https://www.nuget.org/packages/SharpCaster/)\n[![Chocolatey](https://img.shields.io/chocolatey/v/sharpcaster.svg?style=flat\u0026include_prereleases)](https://community.chocolatey.org/packages/sharpcaster)\n[![Homebrew Tap](https://img.shields.io/badge/homebrew-tap-brightgreen?logo=homebrew\u0026style=flat)](https://github.com/Tapanila/homebrew-sharpcaster)\n\nSharpCaster is a cross-platform toolkit for communicating with Google Chromecast devices. It includes:\n\n- C# SDK (NuGet): A library for .NET apps to discover, connect, launch apps, and control media on Chromecast devices.\n- Sharpcaster Console (CLI): A cross‑platform command-line app for controlling Chromecast from your terminal, distributed via Chocolatey and Homebrew.\n\nC# SDK supports .NET Standard 2.0 and .NET 9 (including Native AOT).\nSharpcaster console is built with Native AOT for fast startup, low memory usage and doesn't require .NET installed, works on Windows, macOS, and Linux.\n\n## Table of Contents\n- [Features](#features)\n- [Installation](#installation)\n  - [C# SDK (NuGet)](#c-sdk-nuget)\n  - [Sharpcaster Console (CLI)](#sharpcaster-console-cli)\n- [Quick Start](#quick-start)\n- [Comprehensive Examples](#comprehensive-examples)\n- [Media Queue Management](#media-queue-management)\n- [Volume Control](#volume-control)\n- [Event Handling](#event-handling)\n- [Custom Chromecast Channels](#custom-chromecast-channels)\n- [Troubleshooting](#troubleshooting)\n- [Contributing](#contributing)\n- [License](#license)\n\n## Features\n\n✅ **Device Discovery**: Automatic discovery of Chromecast devices on your local network using mDNS  \n✅ **Media Control**: Complete media playback control (play, pause, stop, seek, volume)  \n✅ **Queue Management**: Support for media queues with navigation (next, previous, shuffle, repeat)  \n✅ **Application Management**: Launch and manage Chromecast applications  \n✅ **Custom Channels**: Extensible architecture for custom Chromecast channels  \n✅ **Cross-Platform**: Compatible with .NET Standard 2.0 and .NET 9  \n✅ **AOT Ready**: Full support for Native AOT compilation in .NET 9  \n✅ **Async/Await**: Modern async programming model throughout  \n✅ **Event-Driven**: Rich event system for real-time status updates  \n\n## Installation\n\n### C# SDK (NuGet)\n\nInstall via NuGet Package Manager:\n```bash\nInstall-Package SharpCaster\n```\n\nOr via .NET CLI:\n```bash\ndotnet add package SharpCaster\n```\n\n### Sharpcaster Console (CLI)\n\nControl Chromecast devices from your terminal.\n\n- Homebrew (macOS/Linux):\n```bash\nbrew tap Tapanila/sharpcaster\nbrew install sharpcaster\n```\n\n- Chocolatey (Windows):\n```powershell\nchoco install sharpcaster --pre\n```\n\nAfter installation, run `sharpcaster` for interactive mode, or use direct commands like:\n```bash\nsharpcaster list\nsharpcaster \"Living Room TV\" play \"https://example.com/video.mp4\" --title \"Sample\"\n```\n\nFor full CLI usage and examples, see `SharpCaster.Console/README.md`.\n\n## Quick Start\n\n### 1. Discover and Connect to Chromecast\n\n```csharp\nusing Sharpcaster;\nusing Sharpcaster.Models;\nusing Sharpcaster.Models.Media;\n\n// Discover Chromecast devices\nvar locator = new ChromecastLocator();\nvar cancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(5)).Token;\nvar chromecasts = await locator.FindReceiversAsync(cancellationToken);\n\nif (!chromecasts.Any())\n{\n    Console.WriteLine(\"No Chromecast devices found\");\n    return;\n}\n\n// Connect to first found device\nvar chromecast = chromecasts.First();\nvar client = new ChromecastClient();\nawait client.ConnectChromecast(chromecast);\nConsole.WriteLine($\"Connected to {chromecast.Name}\");\n```\n\n### 2. Launch Application and Play Media\n\n```csharp\n// Launch the default media receiver app\nawait client.LaunchApplicationAsync(\"CC1AD845\"); // Default Media Receiver\n\n// Create and load media\nvar media = new Media\n{\n    ContentUrl = \"https://commondatastorage.googleapis.com/gtv-videos-bucket/CastVideos/mp4/DesigningForGoogleCast.mp4\",\n    ContentType = \"video/mp4\",\n    Metadata = new MediaMetadata\n    {\n        Title = \"Sample Video\",\n        SubTitle = \"A demonstration video\"\n    }\n};\n\nvar mediaStatus = await client.MediaChannel.LoadAsync(media);\nConsole.WriteLine($\"Media loaded: {mediaStatus.PlayerState}\");\n```\n\n## Comprehensive Examples\n\n### Complete Media Player Example\n\n```csharp\npublic class ChromecastMediaPlayer\n{\n    private ChromecastClient _client;\n    private ChromecastReceiver _device;\n\n    public async Task\u003cbool\u003e ConnectAsync(string deviceName = null)\n    {\n        try\n        {\n            var locator = new ChromecastLocator();\n            var devices = await locator.FindReceiversAsync(CancellationToken.None);\n            \n            _device = deviceName != null \n                ? devices.FirstOrDefault(d =\u003e d.Name.Contains(deviceName))\n                : devices.FirstOrDefault();\n\n            if (_device == null) return false;\n\n            _client = new ChromecastClient();\n            await _client.ConnectChromecast(_device);\n            \n            // Subscribe to events\n            _client.MediaChannel.StatusChanged += OnMediaStatusChanged;\n            _client.Disconnected += OnDisconnected;\n            \n            return true;\n        }\n        catch (Exception ex)\n        {\n            Console.WriteLine($\"Connection failed: {ex.Message}\");\n            return false;\n        }\n    }\n\n    public async Task PlayVideoAsync(string url, string title = null)\n    {\n        await _client.LaunchApplicationAsync(\"CC1AD845\"); // Default Media Receiver\n        \n        var media = new Media\n        {\n            ContentUrl = url,\n            ContentType = GetContentType(url),\n            Metadata = new MediaMetadata\n            {\n                Title = title ?? Path.GetFileNameWithoutExtension(url),\n                MetadataType = MetadataType.Movie\n            }\n        };\n\n        await _client.MediaChannel.LoadAsync(media);\n    }\n\n    public async Task PlayAudioAsync(string url, string title = null, string artist = null)\n    {\n        await _client.LaunchApplicationAsync(\"CC1AD845\");\n        \n        var media = new Media\n        {\n            ContentUrl = url,\n            ContentType = GetContentType(url),\n            Metadata = new MusicTrackMetadata\n            {\n                Title = title ?? Path.GetFileNameWithoutExtension(url),\n                Artist = artist,\n                MetadataType = MetadataType.MusicTrack\n            }\n        };\n\n        await _client.MediaChannel.LoadAsync(media);\n    }\n\n    private void OnMediaStatusChanged(object sender, MediaStatus status)\n    {\n        Console.WriteLine($\"Media Status: {status.PlayerState} - {status.CurrentTime:F1}s\");\n    }\n\n    private void OnDisconnected(object sender, EventArgs e)\n    {\n        Console.WriteLine(\"Disconnected from Chromecast\");\n    }\n\n    private static string GetContentType(string url)\n    {\n        var extension = Path.GetExtension(url).ToLower();\n        return extension switch\n        {\n            \".mp4\" =\u003e \"video/mp4\",\n            \".mp3\" =\u003e \"audio/mpeg\",\n            \".wav\" =\u003e \"audio/wav\",\n            \".webm\" =\u003e \"video/webm\",\n            _ =\u003e \"video/mp4\"\n        };\n    }\n}\n```\n\n## Media Queue Management\n\nSharpCaster supports advanced queue operations for playlist-style media playback:\n\n```csharp\n// Create a media queue\nvar queueItems = new[]\n{\n    new QueueItem\n    {\n        Media = new Media\n        {\n            ContentUrl = \"https://example.com/song1.mp3\",\n            ContentType = \"audio/mpeg\",\n            Metadata = new MusicTrackMetadata { Title = \"Song 1\", Artist = \"Artist 1\" }\n        }\n    },\n    new QueueItem\n    {\n        Media = new Media\n        {\n            ContentUrl = \"https://example.com/song2.mp3\", \n            ContentType = \"audio/mpeg\",\n            Metadata = new MusicTrackMetadata { Title = \"Song 2\", Artist = \"Artist 2\" }\n        }\n    }\n};\n\n// Load queue with repeat mode\nawait client.MediaChannel.QueueLoadAsync(queueItems, 0, RepeatModeType.ALL);\n\n// Navigate through queue\nawait client.MediaChannel.QueueNextAsync();  // Next track\nawait client.MediaChannel.QueuePrevAsync();  // Previous track\n\n// Get queue information\nvar itemIds = await client.MediaChannel.QueueGetItemIdsAsync();\nvar items = await client.MediaChannel.QueueGetItemsAsync(itemIds);\n```\n\n## Volume Control\n\n```csharp\n// Get current volume\nvar status = await client.ReceiverChannel.GetChromecastStatusAsync();\nConsole.WriteLine($\"Current volume: {status.Volume.Level:P0}\");\n\n// Set volume (0.0 to 1.0)\nawait client.ReceiverChannel.SetVolumeAsync(0.5f);\n\n// Mute/unmute\nawait client.ReceiverChannel.SetMutedAsync(true);\nawait client.ReceiverChannel.SetMutedAsync(false);\n```\n\n## Event Handling\n\nSharpCaster provides rich event support for real-time updates:\n\n```csharp\n// Media events\nclient.MediaChannel.StatusChanged += (sender, status) =\u003e\n{\n    Console.WriteLine($\"Player State: {status.PlayerState}\");\n    Console.WriteLine($\"Current Time: {status.CurrentTime}s\");\n    Console.WriteLine($\"Duration: {status.Media?.Duration}s\");\n};\n\n// Connection events  \nclient.Disconnected += (sender, args) =\u003e\n{\n    Console.WriteLine(\"Connection lost to Chromecast\");\n};\n\n// Application events\nclient.ReceiverChannel.StatusChanged += (sender, status) =\u003e\n{\n    foreach (var app in status.Applications ?? [])\n    {\n        Console.WriteLine($\"Running app: {app.DisplayName} ({app.AppId})\");\n    }\n};\n```\n\n## Custom Chromecast Channels\n\nCreate custom channels for specialized applications:\n\n```csharp\npublic class CustomGameChannel : ChromecastChannel\n{\n    public CustomGameChannel(ILogger\u003cCustomGameChannel\u003e logger) \n        : base(\"custom.game\", logger) { }\n\n    public async Task SendGameCommandAsync(string command, object data)\n    {\n        var message = new { type = command, data = data };\n        await SendAsync(JsonSerializer.Serialize(message));\n    }\n\n    protected override async Task OnMessageReceivedAsync(string message, string messageType)\n    {\n        // Handle custom game messages\n        var gameMessage = JsonSerializer.Deserialize\u003cGameMessage\u003e(message);\n        // Process game-specific logic\n    }\n}\n\n// Register and use custom channel\nvar gameChannel = new CustomGameChannel(logger);\nclient.RegisterChannel(gameChannel);\n```\n\nYou can also reverse engineer existing channels:\n\n1. In Chrome, go to `chrome://net-export/`\n2. Select 'Include raw bytes (will include cookies and credentials)'\n3. Click 'Start Logging to Disk'\n4. Cast from your favorite web app\n5. Stop logging and open the log in [netlog-viewer](https://netlog-viewer.appspot.com/)\n6. Search for `type:SOCKET` and find familiar JSON data\n7. Collect the exchanged JSON\n8. Create a new class inheriting from `ChromecastChannel` and implement your logic\n\n## Troubleshooting\n\n### Common Issues and Solutions\n\n**Device Discovery Issues:**\n```csharp\n// Increase discovery timeout for slow networks\nvar cancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(10)).Token;\nvar devices = await locator.FindReceiversAsync(cancellationToken);\n\n// Manually specify device if discovery fails\nvar manualDevice = new ChromecastReceiver\n{\n    Name = \"My Chromecast\",\n    DeviceUri = new Uri(\"http://192.168.1.100\"),\n    Port = 8009\n};\n```\n\n**Connection Timeouts:**\n```csharp\n// Enable logging for debugging\nvar loggerFactory = LoggerFactory.Create(builder =\u003e \n    builder.AddConsole().SetMinimumLevel(LogLevel.Debug));\n    \nvar client = new ChromecastClient(loggerFactory);\n```\n\n**Media Loading Failures:**\n- Ensure media URLs are publicly accessible\n- Verify correct `ContentType` is specified\n- Check that the Chromecast supports the media format\n- Use HTTPS URLs when possible\n\n**Network Issues:**\n- Ensure device is on the same network as Chromecast\n- Check firewall settings (port 8009 must be accessible)\n- Verify mDNS/Bonjour is enabled on the network\n\n### Error Handling Best Practices\n\n```csharp\ntry\n{\n    await client.MediaChannel.LoadAsync(media);\n}\ncatch (TimeoutException)\n{\n    Console.WriteLine(\"Request timed out - check network connection\");\n}\ncatch (InvalidOperationException ex)\n{\n    Console.WriteLine($\"Invalid operation: {ex.Message}\");\n}\ncatch (Exception ex)\n{\n    Console.WriteLine($\"Unexpected error: {ex.Message}\");\n}\n```\n\n## Demo\n\n![SharpCaster Simple demo](https://raw.githubusercontent.com/tapanila/SharpCaster/master/Assets/SharpCaster.Simple.Demo.gif)\n\n## Supported Platforms\n\n- **.NET Standard 2.0** - Maximum compatibility across .NET implementations\n- **.NET 9** - Latest features including Native AOT support\n- **Compatible with**: .NET Framework 4.6.1+, .NET Core 2.0+, .NET 5+, Xamarin, Unity\n\n## Contributing\n\nWe welcome contributions! Here's how you can help:\n\n1. **Fork** the repository\n2. **Create** a feature branch (`git checkout -b feature/AmazingFeature`)\n3. **Commit** your changes (`git commit -m 'Add some AmazingFeature'`)\n4. **Push** to the branch (`git push origin feature/AmazingFeature`)\n5. **Open** a Pull Request\n\n### Development Setup\n\n```bash\ngit clone https://github.com/Tapanila/SharpCaster.git\ncd SharpCaster\ndotnet restore\ndotnet build\ndotnet test\n```\n\n### Testing\n\nTests require a physical Chromecast device on the network:\n```bash\ndotnet test\n```\nSome of the tests may require few tries to pass due to network conditions.\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE.txt) file for details.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftapanila%2Fsharpcaster","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftapanila%2Fsharpcaster","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftapanila%2Fsharpcaster/lists"}