Skip to content

iOS Screen Sharing Not Working - Broadcast Extension Fails After Countdown #2414

@Yousafkhanzadaa

Description

@Yousafkhanzadaa

The Issue I am facing right now:

  • I press the Shere Screen button
  • RPSystemBroadcastPickerView appears with the Broadcast Upload Extension Name
  • I press on it and the countdown starts (3 - 2 -1)
  • Count down stops, and there is no broadcasting happening

I have been working on this issue for the last 3 weeks and still can't find a working solution.

Solutions I have tried:

  • I used AgoraReplayKitHandler (according to the documentation) (not working)
  • I used SampleHandler with the required configuration and tried different code, and used sample code from different sources, including the sample projects from Agora. (Not working)
  • I tried to create an Extension several times and failed
  • I changed Dart(Flutter) code many times based on different samples from Agora and AI (failed)
  • I tried decreasing the resolution of the video to the lowest didn't work out.

Here is the code sample:

AppDelegate.swift:

import Flutter
import UIKit
import ReplayKit

@main
@objc class AppDelegate: FlutterAppDelegate {
  // private var videoRawDataController: VideoRawDataController?
  
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    // On low power mode, the system force keep the screen on for 30s only,
    // we keep the screen on to help internal testing.
    application.isIdleTimerDisabled = true
    
    let controller = window?.rootViewController as! FlutterViewController
    let screensharingIOSChannel = FlutterMethodChannel(
      name: "com.example.adHocc/screen_sharing",
      binaryMessenger: controller.binaryMessenger
    )
    
    screensharingIOSChannel.setMethodCallHandler { (call: FlutterMethodCall, result: @escaping FlutterResult) in
      if #available(iOS 12.0, *) {
        DispatchQueue.main.async {
          if let url = Bundle.main.url(forResource: nil, withExtension: "appex", subdirectory: "PlugIns"),
             let bundle = Bundle(url: url) {
            let picker = RPSystemBroadcastPickerView(frame: CGRect(x: 0, y: 0, width: 100, height: 200))
            picker.showsMicrophoneButton = true
            picker.preferredExtension = bundle.bundleIdentifier
            for view in picker.subviews {
              if let button = view as? UIButton {
                button.sendActions(for: .allTouchEvents)
              }
            }
          }
        }
      }
    }
 
    GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}

ScreenShare Logic:

import 'dart:async';

import 'package:ad_hoc/services/implementations/platform_screen_sharing_service.dart';
import 'package:agora_rtc_engine/agora_rtc_engine.dart';
import 'package:flutter/foundation.dart';

import '../../../models/models.dart';
import '../../../modules/live_learning/utils/constants.dart'
    show AgoraConstants;
import '../../../utils/logger_helper.dart';
import '../../service_locator.dart';

class AgoraScreenSharingService {
  // Static class-level constants
  static const bool _captureAudio = true;
  static const bool _captureVideo = true;

  RtcEngine? _rtcEngine;
  bool _isScreenSharing = false;
  UserRole? _userRole;

  // Getters
  bool get isScreenSharing => _isScreenSharing;
  RtcEngine? get rtcEngine => _rtcEngine;

  void initialize(RtcEngine rtcEngine, UserRole userRole, int localUid) {
    _rtcEngine = rtcEngine;
    _userRole = userRole;
  }

  Future<void> initializeScreenSharing() async {
    if (_rtcEngine == null) return;

    try {
      // Load screen capture extension for Android
      if (defaultTargetPlatform == TargetPlatform.android) {
        await _rtcEngine!.loadExtensionProvider(
          path: l10n.screen_capture_extension_name,
        );
        appLogMessage(
          l10n.screen_capture_extension_loaded,
          name: 'AgoraScreenSharingService',
        );
      }
    } catch (e) {
      appLogMessage(
        '${l10n.screen_capture_extension_load_failed}: $e',
        name: 'AgoraScreenSharingService',
      );
    }
  }

  Future<void> toggleScreenSharing() async {
    if (_rtcEngine == null || _userRole != UserRole.instructor) return;

    try {
      if (_isScreenSharing) {
        await _stopScreenSharing();
      } else {
        await _startScreenSharing();
      }
    } catch (e) {
      appLogMessage(
        '${l10n.screen_sharing_toggle_failed}: $e',
        name: 'AgoraScreenSharingService',
      );
      _updateScreenSharingState(false);
    }
  }

  Future<void> _startScreenSharing() async {
    if (_rtcEngine == null) return;

    try {
      await _rtcEngine!.setClientRole(
        role: ClientRoleType.clientRoleBroadcaster,
      );

      // Set very low video encoder configuration
      await _rtcEngine!.setVideoEncoderConfiguration(
        const VideoEncoderConfiguration(
          dimensions: VideoDimensions(width: 320, height: 240),
          frameRate: 5,
          bitrate: 100,
        ),
      );

      if (defaultTargetPlatform == TargetPlatform.android) {
        await _startScreenCapture();
      } else if (defaultTargetPlatform == TargetPlatform.iOS) {
        await PlatformScreenSharingService.showBroadcastPicker();
        await _startScreenCapture();
      }

      _updateScreenSharingState(true);
      await _updateChannelMediaOptions(true);
    } catch (e) {
      _updateScreenSharingState(false);
      rethrow;
    }
  }

  Future<void> _stopScreenSharing() async {
    if (_rtcEngine == null) return;

    try {
      await _rtcEngine!.stopScreenCapture();
      await _updateChannelMediaOptions(false);
      _updateScreenSharingState(false);
    } catch (e) {
      rethrow;
    }
  }

  Future<void> _startScreenCapture() async {
    if (_rtcEngine == null) return;

    await _rtcEngine!.startScreenCapture(
      const ScreenCaptureParameters2(
        captureAudio: _captureAudio,
        // audioParams: ScreenAudioParameters(
        //   sampleRate: _defaultSampleRate,
        //   channels: _defaultChannels,
        //   captureSignalVolume: _defaultCaptureSignalVolume,
        // ),
        captureVideo: _captureVideo,
        videoParams: ScreenVideoParameters(
          dimensions: VideoDimensions(
            width: 320, // Very low resolution
            height: 240,
          ),
          frameRate: 5, // Very low frame rate
          bitrate: 100, // Very low bitrate
        ),
      ),
    );
  }

  Future<void> _updateChannelMediaOptions(bool isScreenSharing) async {
    if (_rtcEngine == null) return;

    await _rtcEngine!.updateChannelMediaOptions(
      ChannelMediaOptions(
        publishScreenTrack: isScreenSharing,
        publishSecondaryScreenTrack: isScreenSharing,
        publishCameraTrack: !isScreenSharing,
        publishMicrophoneTrack: !isScreenSharing,
        publishScreenCaptureAudio: isScreenSharing,
        publishScreenCaptureVideo: isScreenSharing,
        clientRoleType: ClientRoleType.clientRoleBroadcaster,
      ),
    );
  }

  void _updateScreenSharingState(bool isSharing) {
    _isScreenSharing = isSharing;
  }

  /// Force stop screen sharing - used when session ends
  Future<void> forceStopScreenSharing() async {
    if (_rtcEngine == null) return;

    try {
      if (_isScreenSharing) {
        await _rtcEngine!.stopScreenCapture();
        await _updateChannelMediaOptions(false);
        _updateScreenSharingState(false);
      }
    } catch (e) {
      rethrow;
    }
  }

  void dispose() {
    // Force stop screen sharing before disposing
    forceStopScreenSharing();

    // Clear references
    _rtcEngine = null;
    _userRole = null;
    _isScreenSharing = false;

    appLogMessage(
      'Screen sharing service disposed',
      name: 'AgoraScreenSharingService',
    );
  }
}

I would really appreciate any help that you offer. Thank you so much.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions