startShareScreen function

Future<void> startShareScreen(
  1. StartShareScreenOptions options
)

Starts the screen sharing process.

Supports the following platforms:

  • Web: Uses browser's getDisplayMedia API
  • Android: Uses MediaProjection API (user will see a permission dialog)
  • Windows/macOS/Linux: Uses native screen capture APIs with source picker
  • iOS: Not supported (requires Broadcast Upload Extension)

@param StartShareScreenOptions options - The options for starting screen sharing. @param StartShareScreenParameters options.parameters - The parameters for screen sharing.

This function displays an alert if screen sharing fails or is not supported. It also calls the streamSuccessScreen function when sharing is successful.

Example:

final options = StartShareScreenOptions(
  parameters: StartShareScreenParameters(
    shared: false,
    showAlert: (msg, type, duration) => print(msg),
    updateShared: (isShared) => print("Shared: $isShared"),
    onWeb: true,
    streamSuccessScreen: (stream, parameters) async => print("Success"),
  ),
 targetWidth: 1920,
 targetHeight: 1080,
);
await startShareScreen(options);

Implementation

Future<void> startShareScreen(StartShareScreenOptions options) async {
  final targetWidth = options.targetWidth ?? 1280;
  final targetHeight = options.targetHeight ?? 720;
  final parameters = options.parameters;
  bool shared = parameters.shared;
  final showAlert = parameters.showAlert;
  final updateShared = parameters.updateShared;
  final streamSuccessScreen = parameters.streamSuccessScreen;

  try {
    // Check if screen sharing is supported on this platform
    if (!_isScreenShareSupported()) {
      if (showAlert != null) {
        showAlert(
          message: _getUnsupportedMessage(),
          type: 'danger',
          duration: 3000,
        );
      }
      return;
    }

    // Platform-specific preparations BEFORE requesting screen capture
    if (Platform.isAndroid) {
      // Android requires a foreground service to be running during screen capture.
      // This is REQUIRED on Android 10+ (API 29) and MANDATORY on Android 14+ (API 34)
      // Without this, the app will crash when trying to start MediaProjection
      await _startAndroidForegroundService();
    }
    // iOS: flutter_webrtc handles ReplayKit via getDisplayMedia when
    // RTCAppGroupIdentifier + RTCScreenSharingExtension are set in Info.plist.
    // The system broadcast picker appears automatically.

    // Request screen capture using getDisplayMedia
    // This works on Web, Android, iOS, Windows, macOS, and Linux
    try {
      MediaStream stream;

      // Desktop platforms need to use desktopCapturer to select a source
      if (_needsDesktopCapturer()) {
        final context = options.context;
        if (context == null) {
          // No context provided - try to get sources directly and use first screen
          final sources = await desktopCapturer.getSources(
            types: [SourceType.Screen],
          );

          if (sources.isEmpty) {
            await _stopAndroidForegroundService();
            throw Exception('No screens available to share');
          }

          // Use the first screen
          stream = await navigator.mediaDevices.getDisplayMedia({
            'video': {
              'deviceId': {'exact': sources.first.id},
              'width': targetWidth,
              'height': targetHeight,
              'frameRate': 30
            },
            'audio': false
          });
        } else {
          // Show screen picker dialog
          // ignore: use_build_context_synchronously
          final selectedSource = await _showScreenPickerDialog(context);
          if (selectedSource == null) {
            // User cancelled
            await _stopAndroidForegroundService();
            updateShared(false);
            return;
          }

          stream = await navigator.mediaDevices.getDisplayMedia({
            'video': {
              'deviceId': {'exact': selectedSource.id},
              'width': targetWidth,
              'height': targetHeight,
              'frameRate': 30
            },
            'audio': false
          });
        }
      } else if (kIsWeb) {
        // Web uses standard getDisplayMedia
        stream = await navigator.mediaDevices.getDisplayMedia({
          'video': {
            'cursor': 'always',
            'width': targetWidth,
            'height': targetHeight,
            'frameRate': 30
          },
          'audio': false
        });
      } else if (Platform.isIOS) {
        // iOS uses ReplayKit via flutter_webrtc's native getDisplayMedia.
        // flutter_webrtc reads RTCAppGroupIdentifier & RTCScreenSharingExtension
        // from Info.plist and shows the system broadcast picker.
        // The BroadcastExtension captures frames and sends them over a Unix socket.
        //
        // IMPORTANT: deviceId MUST start with "broadcast" to trigger
        // FlutterBroadcastScreenCapturer instead of FlutterRPScreenRecorder.
        // Without this, it uses in-app ReplayKit which causes "hall of mirrors".
        stream = await navigator.mediaDevices.getDisplayMedia({
          'video': {
            'deviceId': 'broadcast',
          },
          'audio': false,
        });
      } else {
        // Android and other platforms use standard getDisplayMedia
        // flutter_webrtc 1.2.1+ supports these natively
        stream = await navigator.mediaDevices.getDisplayMedia({
          'video': {
            'cursor': 'always',
            'width': targetWidth,
            'height': targetHeight,
            'frameRate': 30
          },
          'audio': false
        });
      }

      // Produce the track for all platforms
      try {
        final optionsStream = StreamSuccessScreenOptions(
          stream: stream,
          parameters: parameters,
        );
        await streamSuccessScreen(optionsStream);
      } catch (e) {
        rethrow;
      }
      shared = true;
    } catch (error) {
      shared = false;

      // Stop foreground service on error
      await _stopAndroidForegroundService();

      String errorMessage = 'Could not share screen, check and retry';

      // Provide more specific error messages
      if (error.toString().contains('NotAllowedError') ||
          error.toString().contains('Permission')) {
        errorMessage =
            'Screen sharing permission denied. Please allow screen capture and try again.';
      } else if (error.toString().contains('NotFoundError') ||
          error.toString().contains('source not found')) {
        errorMessage = 'No screen found to share.';
      } else if (error.toString().contains('NotSupportedError')) {
        errorMessage = 'Screen sharing is not supported on this device.';
      } else if (error.toString().contains('cancelled') ||
          error.toString().contains('canceled')) {
        // User cancelled - not an error
        updateShared(false);
        return;
      }

      showAlert?.call(
        message: errorMessage,
        type: 'danger',
        duration: 3000,
      );
    }

    // Update the shared variable
    updateShared(shared);
  } catch (error) {
    rethrow;
  }
}