feat(cast): add Screenbox.Casting library with Chromecast and DLNA support - #879
Draft
huynhsontung wants to merge 5 commits into
Draft
feat(cast): add Screenbox.Casting library with Chromecast and DLNA support#879huynhsontung wants to merge 5 commits into
huynhsontung wants to merge 5 commits into
Conversation
huynhsontung
marked this pull request as draft
May 7, 2026 11:08
huynhsontung
changed the base branch from
main
to
copilot/migrate-chromecast-casting-feature
May 7, 2026 11:09
huynhsontung
force-pushed
the
copilot/migrate-chromecast-casting-feature
branch
from
May 7, 2026 16:25
2cb0a9d to
d932110
Compare
…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
force-pushed
the
standalone-casting-project
branch
from
May 7, 2026 16:28
45277c4 to
4c94f40
Compare
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR introduces a new
Screenbox.CastingUAP class library that abstracts media casting protocols (Chromecast and DLNA/UPnP) behind protocol-agnostic interfaces, and refactorsScreenbox.Coreto eliminate all direct SharpCaster type references from ViewModels and the service layer.Motivation
The previous implementation coupled
CastControlViewModel,PlayerControlsViewModel,SeekBarViewModel, andVolumeViewModeldirectly to SharpCaster'sChromecastClient. 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 ofICastDevice,ICastDeviceLocator, andICastSession.New
Screenbox.CastinglibraryProtocol-agnostic abstractions
ICastDevice— name, type, availabilityICastDeviceLocator— device discovery withDeviceFound/DeviceLostevents andConnectAsync()ICastSession— full playback control + state properties +PlaybackEnded/DisconnectedeventsIMediaStreamingService— HTTP media proxy server for local file streamingCastDeviceTypeenum:Chromecast,DlnaChromecast implementation
Wraps SharpCaster 3.0.0:
ChromecastDevice,ChromecastDeviceLocator(mDNS viaChromecastLocator),ChromecastSession(static factoryCreateAsync()connects, launches Default Media Receiver, loads media, monitorsMediaChannel.StatusChangedandReceiverChannel.ReceiverStatusChanged).DLNA/UPnP implementation
UWP-native implementation (avoids RSSDP which excludes its
SocketFactoryon UWP):DlnaDeviceLocator— SSDP M-SEARCH viaDatagramSocket, fetches device description XML, re-discovers every 30sDlnaAvTransportClient— SOAP: SetAVTransportURI, Play, Pause, Stop, Seek, GetPositionInfo, GetTransportInfoDlnaRenderingControlClient— 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 usingStreamSocketListener, range requests, direct pass-through for http/https sourcesScreenbox.CorechangesICastServiceCreateLocators(),ConnectAndCastAsync(),DisconnectAsync()CastServiceCastDeviceTypeCastContextChromecastClient? Client→ICastSession? Session; 500msDispatcherQueueTimerpolls session state into observable propertiesCastControlViewModelList<ICastDeviceLocator>andObservableCollection<ICastDevice>PlayerControlsViewModelICastService; play/pause now calls_castContext.Session.PlayAsync/PauseAsync()SeekBarViewModelICastService; seek now calls_castContext.Session.SeekAsync()VolumeViewModelICastService; volume/mute now calls_castContext.Session.SetVolumeAsync/SetMuteAsync()RendererEventArgsICastDeviceinstead ofRendererScreenbox.Core.csprojRenderer.cs/RendererWatcher.cs/streaming service compile entriesServiceHelpersIMediaStreamingServicefrom Screenbox.CastingScreenboxapp changesRendererGlyphConverter— returns TV glyph (uE7F4`) for Chromecast, speaker glyph (uE767`) for DLNACastControl.xaml—x:DataTypeupdated frommodels:Renderertocasting:ICastDeviceScreenbox.csproj— added direct ProjectReference to Screenbox.CastingScreenbox.sln— added Screenbox.Casting with all 10 platform configurationsBuild Status
✅ All three projects compile successfully (Screenbox.Casting, Screenbox.Core, Screenbox)