no_screen_mirror
A Flutter plugin to detect screen mirroring (AirPlay, Miracast), external display connections (HDMI, USB-C, DisplayPort, VGA, DVI), and screen sharing in video calls.
Features
- Detect screen mirroring (AirPlay on iOS/macOS, Miracast on Android/Windows)
- Detect external display connections (HDMI, USB-C, DisplayPort, VGA, DVI)
- Detect screen sharing and recording (Zoom, Teams, Slack, Discord, OBS, etc.)
- Report the total number of connected displays
- Real-time streaming of display state changes
- Configurable polling interval for battery-sensitive apps
- Custom screen sharing process names for extensible detection
- Platform capability reporting at runtime
- Cross-platform: Android, iOS, macOS, Linux, Windows, and Web
Platform Support
| Platform | Mirroring | External Display | Screen Sharing | Detection Method |
|---|---|---|---|---|
| Android | Yes (Miracast) | Yes | Yes (API 34+) | DisplayManager + MediaRouter + ScreenCaptureCallback |
| iOS | Yes (AirPlay) | Yes | Yes (iOS 11+) | UIScreen notifications + isCaptured |
| macOS | Yes | Yes | Yes | CoreGraphics + process detection |
| Linux | No | Yes | Yes | /sys/class/drm + /proc scanning |
| Windows | Yes (Miracast) | Yes | Yes | Win32 Display Config + process scanning |
| Web | No | Chromium 100+ | No | Screen.isExtended API |
Installation
Add no_screen_mirror to your pubspec.yaml:
dependencies:
no_screen_mirror: ^0.1.0
Then run:
flutter pub get
Permissions
No special permissions are required on any platform. The plugin uses only standard system APIs that are available without additional entitlements or manifest declarations.
Usage
Basic Example
import 'package:no_screen_mirror/no_screen_mirror.dart';
import 'package:no_screen_mirror/mirror_snapshot.dart';
final plugin = NoScreenMirror.instance;
// Start listening for display changes
await plugin.startListening();
// Listen to the mirror stream
plugin.mirrorStream.listen((MirrorSnapshot snapshot) {
print('Screen mirrored: ${snapshot.isScreenMirrored}');
print('External display: ${snapshot.isExternalDisplayConnected}');
print('Screen shared: ${snapshot.isScreenShared}');
print('Display count: ${snapshot.displayCount}');
});
// Stop listening when done
await plugin.stopListening();
Configurable Polling Interval
Control how often the plugin scans for changes on platforms that use polling (Linux, Windows, macOS, Web). iOS and Android are event-driven and ignore this setting.
await plugin.startListening(
pollingInterval: const Duration(seconds: 5), // Default: 2 seconds
);
Custom Screen Sharing Process Names
Add your own process names or bundle IDs to the detection list. This extends (does not replace) the built-in list.
await plugin.startListening(
customScreenSharingProcesses: [
'my-conferencing-app', // Linux process name
'com.example.myapp', // macOS bundle ID
'MyApp.exe', // Windows process name
],
);
Platform Capabilities
Check at runtime what the current platform can detect:
import 'package:no_screen_mirror/mirror_capabilities.dart';
final caps = NoScreenMirror.platformCapabilities;
if (caps.canDetectScreenSharing) {
// Enable screen sharing UI
}
print(caps.notes); // Platform-specific notes
With StreamSubscription
import 'dart:async';
import 'package:no_screen_mirror/no_screen_mirror.dart';
import 'package:no_screen_mirror/mirror_snapshot.dart';
class _MyWidgetState extends State<MyWidget> {
final _plugin = NoScreenMirror.instance;
StreamSubscription<MirrorSnapshot>? _subscription;
MirrorSnapshot? _snapshot;
Future<void> _startListening() async {
await _plugin.startListening();
_subscription = _plugin.mirrorStream.listen((snapshot) {
if (!mounted) return;
setState(() => _snapshot = snapshot);
});
}
Future<void> _stopListening() async {
await _plugin.stopListening();
_subscription?.cancel();
_subscription = null;
}
@override
void dispose() {
_subscription?.cancel();
super.dispose();
}
}
API Reference
NoScreenMirror
| Member | Type | Description |
|---|---|---|
instance |
NoScreenMirror |
Singleton accessor |
platformCapabilities |
MirrorCapabilities |
Runtime platform capability info (static) |
mirrorStream |
Stream<MirrorSnapshot> |
Stream of display state updates |
startListening() |
Future<void> |
Begin monitoring for display changes |
stopListening() |
Future<void> |
Stop monitoring |
startListening Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
pollingInterval |
Duration |
Duration(seconds: 2) |
How often to scan on polling-based platforms |
customScreenSharingProcesses |
List<String> |
[] |
Additional process names to detect as screen sharing |
MirrorSnapshot
| Property | Type | Description |
|---|---|---|
isScreenMirrored |
bool |
Whether the screen is being mirrored (AirPlay/Miracast) |
isExternalDisplayConnected |
bool |
Whether an external display is connected (HDMI, USB-C, etc.) |
isScreenShared |
bool |
Whether screen sharing or recording is active |
displayCount |
int |
Total number of connected displays |
MirrorCapabilities
| Property | Type | Description |
|---|---|---|
canDetectMirroring |
bool |
Whether mirroring detection is supported |
canDetectExternalDisplay |
bool |
Whether external display detection is supported |
canDetectScreenSharing |
bool |
Whether screen sharing detection is supported |
platform |
String |
Current platform identifier |
notes |
String |
Platform-specific limitations and notes |
Platform Notes
Android
Requires Android 4.2 (API 17) or later. Uses DisplayManager for external display detection and MediaRouter for Miracast/wireless mirroring detection. Screen sharing detection uses Activity.ScreenCaptureCallback which requires Android 14+ (API 34) — on older versions, isScreenShared is always false.
iOS
Uses UIScreen notifications for display connection events and isCaptured (iOS 11+) for AirPlay mirroring and screen sharing detection. The isCaptured property covers screen recording, AirPlay mirroring, and screen sharing by third-party apps.
macOS
Uses CoreGraphics APIs (CGDisplayIsBuiltin, CGDisplayMirrorsDisplay) to distinguish external displays and detect mirroring. Screen sharing is detected by checking running application bundle IDs (Zoom, Teams, Slack, Discord, OBS, QuickTime, Loom). Custom bundle IDs can be added via customScreenSharingProcesses.
Linux
Scans /sys/class/drm/ for display connectors. Supports eDP, LVDS, DSI (built-in) and HDMI, DP, VGA, DVI (external). Screen mirroring detection is not available (always returns false) — there is no kernel-level mirroring API. Screen sharing is detected by scanning /proc/*/comm for known process names (zoom, teams, slack, discord, obs, ffmpeg, etc.).
Windows
Uses Win32 Display Configuration APIs for external display and Miracast detection via QueryDisplayConfig. Screen sharing is detected by scanning running processes via CreateToolhelp32Snapshot for known executables (Zoom.exe, Teams.exe, slack.exe, Discord.exe, obs64.exe, ffmpeg.exe, etc.).
Web
Uses the Screen.isExtended API available in Chromium 100+. Safari and Firefox are not supported (values default to false). Screen mirroring and screen sharing detection are not available in browsers. The plugin also listens for visibilitychange events to re-scan when the tab is shown/hidden.
Built-in Screen Sharing Process Lists
macOS (Bundle IDs)
us.zoom.xos, com.microsoft.teams, com.microsoft.teams2, com.tinyspeck.slackmacgap, com.hnc.Discord, com.obsproject.obs-studio, com.apple.QuickTimePlayerX, com.loom.desktop
Linux (Process Names)
zoom, teams, teams-for-linux, slack, discord, obs, ffmpeg, simplescreenrecorder, kazam, peek, recordmydesktop, vokoscreen
Windows (Executable Names)
Zoom.exe, CptHost.exe, Teams.exe, ms-teams.exe, slack.exe, Discord.exe, obs64.exe, obs32.exe, ffmpeg.exe