Skip to content

feat(cast): add Screenbox.Casting library with Chromecast and DLNA support - #879

Draft
huynhsontung wants to merge 5 commits into
copilot/migrate-chromecast-casting-featurefrom
standalone-casting-project
Draft

feat(cast): add Screenbox.Casting library with Chromecast and DLNA support#879
huynhsontung wants to merge 5 commits into
copilot/migrate-chromecast-casting-featurefrom
standalone-casting-project

Conversation

@huynhsontung

@huynhsontung huynhsontung commented May 7, 2026

Copy link
Copy Markdown
Owner

Summary

This PR introduces a new Screenbox.Casting UAP class library that abstracts media casting protocols (Chromecast and DLNA/UPnP) behind protocol-agnostic interfaces, and refactors Screenbox.Core to eliminate all direct SharpCaster type references from ViewModels and the service layer.

Motivation

The previous implementation coupled CastControlViewModel, PlayerControlsViewModel, SeekBarViewModel, and VolumeViewModel directly to SharpCaster's ChromecastClient. This made it impossible to add DLNA support without significant ViewModel changes. By introducing a thin abstraction layer, adding new casting protocols in the future requires only a new implementation of ICastDevice, ICastDeviceLocator, and ICastSession.

New Screenbox.Casting library

Protocol-agnostic abstractions

  • ICastDevice — name, type, availability
  • ICastDeviceLocator — device discovery with DeviceFound/DeviceLost events and ConnectAsync()
  • ICastSession — full playback control + state properties + PlaybackEnded/Disconnected events
  • IMediaStreamingService — HTTP media proxy server for local file streaming
  • CastDeviceType enum: Chromecast, Dlna

Chromecast implementation

Wraps SharpCaster 3.0.0: ChromecastDevice, ChromecastDeviceLocator (mDNS via ChromecastLocator), ChromecastSession (static factory CreateAsync() connects, launches Default Media Receiver, loads media, monitors MediaChannel.StatusChanged and ReceiverChannel.ReceiverStatusChanged).

DLNA/UPnP implementation

UWP-native implementation (avoids RSSDP which excludes its SocketFactory on UWP):

  • DlnaDeviceLocator — SSDP M-SEARCH via DatagramSocket, fetches device description XML, re-discovers every 30s
  • DlnaAvTransportClient — SOAP: SetAVTransportURI, Play, Pause, Stop, Seek, GetPositionInfo, GetTransportInfo
  • DlnaRenderingControlClient — SOAP: SetVolume/GetVolume/SetMute (0–100 ↔ 0.0–1.0)
  • DlnaSession — 1s polling timer (DLNA has no push model)

Media streaming

  • MediaStreamingService — HTTP/1.1 server using StreamSocketListener, range requests, direct pass-through for http/https sources

Screenbox.Core changes

File Change
ICastService Replaced SharpCaster-typed methods with CreateLocators(), ConnectAndCastAsync(), DisconnectAsync()
CastService Rewritten to create protocol locators and delegate by CastDeviceType
CastContext Replaced ChromecastClient? ClientICastSession? Session; 500ms DispatcherQueueTimer polls session state into observable properties
CastControlViewModel Rewritten using List<ICastDeviceLocator> and ObservableCollection<ICastDevice>
PlayerControlsViewModel Removed ICastService; play/pause now calls _castContext.Session.PlayAsync/PauseAsync()
SeekBarViewModel Removed ICastService; seek now calls _castContext.Session.SeekAsync()
VolumeViewModel Removed ICastService; volume/mute now calls _castContext.Session.SetVolumeAsync/SetMuteAsync()
RendererEventArgs Updated to carry ICastDevice instead of Renderer
Screenbox.Core.csproj Removed SharpCaster reference; added ProjectReference to Screenbox.Casting; removed Renderer.cs/RendererWatcher.cs/streaming service compile entries
ServiceHelpers Registers IMediaStreamingService from Screenbox.Casting

Screenbox app changes

  • RendererGlyphConverter — returns TV glyph (uE7F4`) for Chromecast, speaker glyph (uE767`) for DLNA
  • CastControl.xamlx:DataType updated from models:Renderer to casting:ICastDevice
  • Screenbox.csproj — added direct ProjectReference to Screenbox.Casting
  • Screenbox.sln — added Screenbox.Casting with all 10 platform configurations

Build Status

All three projects compile successfully (Screenbox.Casting, Screenbox.Core, Screenbox)

  • 72 warnings (pre-existing, unrelated to changes)
  • 1 error: APPX1607 manifest error (pre-existing in CLI builds, expected)
  • No C# compilation errors introduced by this refactoring

@dosubot dosubot Bot added size:XXL This PR changes 1000+ lines, ignoring generated files. enhancement New feature or request labels May 7, 2026
@huynhsontung
huynhsontung marked this pull request as draft May 7, 2026 11:08
@huynhsontung
huynhsontung changed the base branch from main to copilot/migrate-chromecast-casting-feature May 7, 2026 11:09
@huynhsontung
huynhsontung force-pushed the copilot/migrate-chromecast-casting-feature branch from 2cb0a9d to d932110 Compare May 7, 2026 16:25
huynhsontung and others added 3 commits May 7, 2026 09:28
…pport

Introduce a new Screenbox.Casting UAP class library that abstracts media
casting protocols behind protocol-agnostic interfaces, and refactor
Screenbox.Core to use these abstractions instead of SharpCaster types.

- Add ICastDevice, ICastDeviceLocator, ICastSession abstractions
- Add CastDeviceType enum (Chromecast, Dlna)
- Add CastDeviceFoundEventArgs, CastDeviceRemovedEventArgs events
- Implement ChromecastDevice, ChromecastDeviceLocator, ChromecastSession
  wrapping SharpCaster 3.0.0
- Implement DlnaDevice, DlnaDeviceLocator (SSDP via DatagramSocket),
  DlnaAvTransportClient, DlnaRenderingControlClient, DlnaSession
  using UWP-native SOAP/SSDP (avoids RSSDP UWP incompatibility)

- Rewrite ICastService: CreateLocators(), ConnectAndCastAsync(),
  DisconnectAsync() — all SharpCaster types removed from the interface
- Rewrite CastService to delegate by CastDeviceType
- Rewrite CastContext: replace ChromecastClient? Client with
  ICastSession? Session; use DispatcherQueueTimer polling at 500 ms
- Rewrite CastControlViewModel using ICastDeviceLocator list and
  ICastDevice observable collection
- Remove ICastService from PlayerControlsViewModel, SeekBarViewModel,
  VolumeViewModel constructors; use _castContext.Session directly
- Update RendererEventArgs to carry ICastDevice instead of Renderer
- Remove SharpCaster package reference; add ProjectReference to
  Screenbox.Casting; remove Renderer.cs and RendererWatcher.cs

- Update RendererGlyphConverter to return TV glyph for Chromecast
  and speaker glyph for DLNA based on ICastDevice.Type
- Update CastControl.xaml DataTemplate x:DataType to ICastDevice
- Add direct ProjectReference to Screenbox.Casting for XAML compiler
- Add Screenbox.Casting to Screenbox.sln with all platform configs

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move the HTTP media proxy server into the Screenbox.Casting library so that
all casting-related infrastructure is colocated. This ensures media streaming
is treated as a critical dependency of the casting system.

Changes:
- Add IMediaStreamingService interface to Screenbox.Casting.Abstractions
- Move MediaStreamingService implementation to Screenbox.Casting.Services
- Update CastService to use the new Casting-scoped API (StartStreamAsync(source))
- Update ServiceHelpers DI registration to reference Casting namespaces
- Remove old MediaStreamingService types from Screenbox.Core
- Update both project files

This allows the Screenbox.Casting library to be self-contained with all
necessary infrastructure for both device discovery and media delivery.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@huynhsontung
huynhsontung force-pushed the standalone-casting-project branch from 45277c4 to 4c94f40 Compare May 7, 2026 16:28
huynhsontung and others added 2 commits May 7, 2026 13:21
Replace IMediaStreamingService/MediaStreamingService with a cleaner
ICastMediaProxy/CastMediaProxy design that better fits the Casting
library's domain language and lifecycle model.

Key changes:
- Rename IMediaStreamingService → ICastMediaProxy and move to
  Abstractions/; the new interface takes IStorageFile directly (not
  object) and returns an ICastProxyHandle instead of managing start/stop
  on a singleton.
- Add ICastProxyHandle : IDisposable (Abstractions/) - a scoped handle
  that owns the StreamSocketListener; disposing it stops the server and
  releases the port automatically.
- Move implementation to Proxy/CastMediaProxy.cs; CastProxyHandle is a
  private inner class, making each StartAsync call fully independent.
  The proxy is now stateless.
- Remove the TryGetNetworkUri dual-responsibility from the proxy; move it
  to CastService as a private static helper. The proxy's only job is now
  proxying local files.
- Add ProxiedCastSession (private inner class in CastService) that wraps
  ICastSession + ICastProxyHandle?, delegating all members and disposing
  both on Dispose(). This ties the proxy lifetime to the session lifetime
  so explicit StopStream() calls are no longer needed.
- Update ServiceHelpers.cs DI registration:
  IMediaStreamingService/MediaStreamingService → ICastMediaProxy/CastMediaProxy
- Delete Services/ folder (was only ever used for MediaStreamingService)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant