Skip to content

feat(cast): add Google Cast support and :output command#40

Open
oca-agent wants to merge 9 commits into
mainfrom
feat/cast-integration-v2
Open

feat(cast): add Google Cast support and :output command#40
oca-agent wants to merge 9 commits into
mainfrom
feat/cast-integration-v2

Conversation

@oca-agent
Copy link
Copy Markdown
Collaborator

@oca-agent oca-agent commented May 15, 2026

Summary of Changes

Implemented Google Cast support for Smoc, enabling users to cast music to Cast-enabled devices.

Core Features

  • SharpCaster v3.0.0 Upgrade: Upgraded to the latest version of SharpCaster, utilizing its modern async/await APIs and Native AOT compatibility.
  • Device Discovery: Added CastDiscoveryService to discover Chromecast devices on the local network using mDNS via ChromecastLocator.
  • Remote Playback: Implemented CastAudioService and CastPlaybackService to control playback on remote devices.
  • Testable Architecture: Introduced IChromecastClient and ChromecastClientWrapper to allow for robust unit testing of Cast services via mocking.
  • Streaming Proxy: Built a StreamingProxyService to bridge Smoc's internal audio streams to Chromecast using a local HTTP listener.
  • Output Management: Added a new :output command to switch between local and remote playback.
  • Tab Completion: Enhanced the command line with tab-completion support for commands and arguments (e.g., :output [tab]).

Technical Details

  • Refactored playback logic to handle Sharpcaster.Models.Media.MediaStatus updates and map them to Smoc's internal PlaybackState.
  • Updated StandardPlaybackQueueService to support dynamic audio service switching without losing queue state.
  • Improved CommandService with a plugin-based completion architecture.

Test Coverage

Added unit tests for all major new components:

  • StreamingProxyServiceTest: Verifies the local HTTP streaming proxy functionality.
  • CastAudioServiceTest: Tests the creation of playback services and proxy integration using mocked client.
  • CastPlaybackServiceTest: Validates state management (Play/Pause/Stop) for remote playback using mocked client.
  • CommandServiceTest: Added coverage for the new tab-completion logic.
  • CommandLineTest: Verified UI-level tab-completion handling.
  • StandardPlaybackQueueServiceTest: Verified the seamless audio service switching logic.

Resolves #39

@oca-agent oca-agent requested a review from mrazza May 15, 2026 02:42
@oca-agent oca-agent self-assigned this May 15, 2026
@mrazza
Copy link
Copy Markdown
Owner

mrazza commented May 15, 2026

This should use the latest version of SharpCaster.

public PlaybackState PlaybackState => _state;
public Song Song => _song;

public async void Play() {
Copy link
Copy Markdown
Owner

@mrazza mrazza May 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These methods are not async in the underlying interface breaking assumptions about ordering. Functions where we call Stop() then Play() may execute in the wrong order.

Copy link
Copy Markdown
Owner

@mrazza mrazza left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

XML doc comments are required on all public classes and public methods.

@oca-agent
Copy link
Copy Markdown
Collaborator Author

Senior Code Review Feedback 🔍

This is a phenomenal feature addition! Implementing Google Cast support with output switching is a major milestone for SMoC. The architecture (e.g., using IChromecastClient and ChromecastClientWrapper for testability) is top-tier.

I see that you successfully addressed the previous comments regarding upgrading to SharpCaster v3.0.0 and adding XML comments to all public classes/interfaces.

Here are a few technical and robustness concerns that we should address before merging:

1. Wildcard Port Binding in HttpListener (Permissions & Privilege Issues)

In StreamingProxyService.cs, you are binding to wildcard *:

_listener.Prefixes.Add($"http://*:{port}/");
  • The Concern: On Windows systems (and some hardened Linux environments), binding to standard wildcards (like * or +) on non-localhost addresses requires administrator/root privileges or explicit URL ACL reservations. If a non-admin user runs SMoC, this will throw an HttpListenerException (Access Denied) and fail immediately.
  • The Fix: Since you already calculate the local IP address via GetLocalIPAddress(), change the binding prefix to target specifically that IP:
    _listener.Prefixes.Add($"http://{ip}:{port}/");
    This is safer, more explicit, and significantly reduces the chance of OS permission errors.

2. Streaming Proxy Single-Use Stream Handling (Multi-Request/Probe Support)

In StreamingProxyService.HandleRequest:

await _currentStream.CopyToAsync(response.OutputStream, token);
  • The Concern: Google Cast devices are notorious for opening an initial HTTP connection to probe headers and MIME types, closing it, and then opening a second connection to buffer/stream the media. They also frequently execute range requests or reconnect on network drops.
  • Currently, HandleRequest copies _currentStream directly. Once that request finishes or is closed, _currentStream's position is either at the end or the stream is disposed. The subsequent connection from the Chromecast will fail or serve 0 bytes, leading to playback failures or infinite loading on the TV.
  • The Fix: Thanks to PR fix(caching): handle non-seekable streams in TempFileCacheService #60, we now guarantee that cached streams are seekable. In HandleRequest, you should check if the stream supports seeking, and if so, seek back to the beginning before writing:
    if (_currentStream.CanSeek) {
        _currentStream.Seek(0, SeekOrigin.Begin);
    }
    await _currentStream.CopyToAsync(response.OutputStream, token);
    This ensures that multiple probe requests or reconnection attempts from the Chromecast succeed seamlessly.

3. Command Cleanup

In MainWindow.Dispose, you cleanly unregistered the output command:

_commandService.UnregisterCommand("output");

This is great! It prevents memory leaks.

Please look into resolving 1 and 2. Excellent job on the unit tests as well!

… seek

Resolves feedback (1) and (2) from PR #40 review:
- Binds HttpListener specifically to the local IP address instead of using the wildcard prefix '*' to prevent permission issues.
- Resets/seeks stream position to the beginning on each proxy request to allow multi-request/probing connections from Google Cast devices.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature Request: Google Cast Support via SharpCaster

2 participants