Shiny Controls v1.0 - The Ultra Control Suite for .NET MAUI & BlazorO...M...G!
Screen Recording Releases
5.6.0 - TBD
Section titled “5.6.0 - TBD”Initial Release
Section titled “Initial Release”Feature
New
Shiny.ScreenRecorder package — cross-platform screen recording for Android, iOS, Mac Catalyst, macOS and Windows, with Shiny.ScreenRecorder.Linux covering Linux through the xdg-desktop-portal ScreenCast API and Shiny.ScreenRecorder.Blazor covering the browser. Register services.AddScreenRecorder() and inject IScreenRecorder to record the screen to a video file with optional microphone and system audio, pause and resume, and pick a display or window on desktop.Feature
IScreenRecorder.Capabilities publishes a ScreenRecorderCapabilities flags value describing what the current platform can actually do, and a request asking for something outside it throws ScreenRecorderNotSupportedException before any native call happens — because a recording that silently came out without the microphone, at the wrong frame rate, or of the wrong display is worse than one that refused to start. The flags are read off the instance rather than inferred from the target framework, because they differ within a platform as well as between them.Feature
Start(request) returns an IScreenRecording only once frames are genuinely being written, not when the request was accepted — so a consent dialog, a compositor picker or an Android foreground-service promotion all complete first. Stop() returns a ScreenRecordingResult with the path, duration, byte size, encoded dimensions and the MIME type actually produced. Disposing the session without stopping cancels it and deletes the partial file. One recording at a time: Start throws while another is in flight, matching the restriction every platform underneath already has.Feature
IScreenRecording.Faulted reports the OS ending a recording without being asked — the user revoking the capture, the system pre-empting it, the target going away, the encoder failing, or MaxDuration elapsing — and carries whatever was salvaged, so a recording cut short still yields a playable file rather than a silent truncation. This is not an edge case: it is the normal way a screen recording ends on several of these platforms.Feature
Pause()/Resume() close the gap in the timeline rather than leaving a frozen stretch in the middle of the file — later timestamps are shifted back by the paused span. Only the browser pauses natively; everywhere else the capture keeps running and frames are dropped, so a long pause still costs battery. Elapsed excludes the paused span and matches the duration of the finished file.Feature
GetTargets() lists displays, windows and applications on macOS and Windows so an app can offer its own picker, with Target on the request selecting one. Mobile has no concept of a target, and Linux and the browser hand selection to the compositor — both throw here and show their own picker during Start instead.FeatureAndroid
Backed by
MediaProjection into a VirtualDisplay, encoded with MediaCodec (a surface-fed H.264 encoder plus AAC) into a MediaMuxer. MediaCodec rather than the far simpler MediaRecorder for one reason: MediaRecorder.setAudioSource takes a single source and playback capture is not one of them, so app audio is only reachable through AudioRecord + AudioPlaybackCaptureConfiguration — wanting it at all forces the whole pipeline down. Mic and app audio are summed with saturation when both are requested.FeatureAndroid
Consent runs through a transparent, no-history activity in the package, and the required
mediaProjection foreground service is started before getMediaProjection is called — the ordering Android 14 (API 34) made mandatory and which throws SecurityException if reversed. Both are merged into the consuming app’s manifest automatically. Screen consent cannot be pre-granted, so RequestAccess answers only for the microphone and reports AccessState.Unknown for the screen.FeatureiOS
Backed by
RPScreenRecorder.startCapture feeding an AVAssetWriter — not startRecording, which keeps the movie inside ReplayKit and only surrenders it through RPPreviewViewController, a user-facing share sheet that is no use to a library promising a file path. This records the app’s own UI only; system-wide capture needs a Broadcast Upload Extension, a second app target no NuGet package can deliver. The app must be in the foreground, and ReplayKit stopping on an incoming call or a screen lock arrives as Faulted rather than as a truncated file.FeaturemacOS
Backed by ScreenCaptureKit, with two paths. macOS 15+ uses
SCRecordingOutput, which writes the MP4 itself and adds microphone capture — but cannot be paused, since a recording output cannot be detached and reattached mid-file. macOS 12.3–14 pumps SCStream sample buffers through the same AVAssetWriter the Apple mobile backend uses, which can synthesise a pause but has no microphone source. PauseResume and Microphone therefore differ by OS version rather than the modern path being crippled to make the flags uniform. SCShareableContent supplies displays, windows and applications for GetTargets(), and CGPreflightScreenCaptureAccess/CGRequestScreenCaptureAccess drive the Screen Recording prompt — neither is bound in the macOS SDK, so both are reached by LibraryImport.FeatureWindows
Backed by
Windows.Graphics.Capture — a free-threaded Direct3D11CaptureFramePool bridged into a MediaStreamSource and encoded to MP4 by MediaTranscoder, so no frame is ever copied into managed memory. Displays and windows come from IGraphicsCaptureItemInterop rather than the system picker, so GetTargets() returns a real list the app controls. The COM and D3D11 interop is hand-rolled through vtable calls rather than ComImport, keeping the package AOT- and trim-clean.FeatureWindows
Windows records video only.
Windows.Graphics.Capture has no audio path at all — unlike every other platform here, its capture API captures pixels and nothing else. Capabilities reports neither Microphone nor SystemAudio and requesting either throws, rather than silently producing a silent file. System audio would require a hand-written WASAPI loopback capture and is tracked separately.FeatureLinux
Shiny.ScreenRecorder.Linux drives org.freedesktop.portal.ScreenCast over D-Bus for consent and frames, then hands the PipeWire node to gst-launch-1.0 (Wayland and X11) or falls back to ffmpeg -f x11grab on X11. Stopping sends SIGINT rather than SIGKILL, because both encoders need it to flush and write the MP4 index — a killed encoder leaves a file no player will open. Encoder availability, the session type and the PulseAudio monitor source are all probed at runtime, so a machine missing the pieces reports ScreenRecorderCapabilities.None rather than failing when the user presses record. Flatpak sandboxes are not supported.FeatureBlazor
Shiny.ScreenRecorder.Blazor records through getDisplayMedia and MediaRecorder. It is the only platform where pause is native, and the only one where ScreenRecordingResult.FilePath is null — the browser has no filesystem, so OpenRead() streams the blob back through an IJSStreamReference and DownloadRecording(result, fileName) hands it to the user. The container varies by browser and the result says which: Safari and recent Chrome produce MP4/H.264, Firefox produces WebM/VP9. Display audio and the microphone are summed through a WebAudio graph when both are requested, since MediaRecorder encodes only one audio track. Requires a secure context and a user gesture, and Probe() must run once before Capabilities reports anything.Feature
ScreenRecordingResult.OpenRead() is the portable way to read a recording back — it opens the file where there is one and streams the blob out of JavaScript where there is not, so calling code stays the same across every platform. MimeType reports what was actually produced rather than what was asked for.Feature
MaxWidth downscales while preserving aspect ratio, rounding both sides to even because H.264 cannot encode odd dimensions — worth setting on almost any recording, since a modern phone or Retina display at native resolution produces very large files for very little visible gain. When no bitrate is given, one is estimated from the resolution and clamped to a range that keeps screen text legible without producing gigabyte files.

