AgoraIO-Extensions / react-native-agora

React Native around the Agora RTC SDKs for Android and iOS agora
https://www.agora.io
MIT License
620 stars 227 forks source link

Can't share screen using physical IOS Device #792

Closed bagusaff closed 2 months ago

bagusaff commented 2 months ago

Hello, I have problem integrating screenShare on IOS device ( I successfully integrated Android Device to be able to share screen and IOS Device as Client able to see it aswell )

I have followed this documentation : https://docs.agora.io/en/video-calling/core-functionality/screen-sharing?platform=react-native

I did not implement any custom business logic so I skipped this section from documentation : To implement additional business logic, refer to the following code to modify the SampleHandler.h file:

I have also configured my provisioning profile on appstore because I actually develop this feature with react-native-webrtc but decided to move into agora.

Whenever I press "Start Broadcast" it only does countdown 3..2..1 while the button change to "Stop Broadcast" and then nothing happen, and the button will turn back into "Start Broadcast" IMG_0842

I copied useInitRtcEngine hook from here : https://github.com/AgoraIO-Extensions/react-native-agora/blob/main/example/src/examples/hook/hooks/useInitRtcEngine.tsx

My Code Logic :

import {useIsCaptured} from 'react-native-is-screen-captured-ios';

 const isCaptured = useIsCaptured();
 const reqCameraPermission = async () => {
      const result = await requestPermission();
      return result;
    };

    useEffect(() => {
      joinChannel();
    }, []);

    useEffect(() => {
      engine.current.addListener(
        'onLocalVideoStateChanged',
        (
          source: VideoSourceType,
          state: LocalVideoStreamState,
          error: LocalVideoStreamReason,
        ) => {
          if (source === VideoSourceType.VideoSourceScreen) {
            switch (state) {
              case LocalVideoStreamState.LocalVideoStreamStateStopped:
              case LocalVideoStreamState.LocalVideoStreamStateFailed:
                break;
              case LocalVideoStreamState.LocalVideoStreamStateCapturing:
              case LocalVideoStreamState.LocalVideoStreamStateEncoding:
                setSharedContentType('screen');
                break;
            }
          }
        },
      );
      const engineCopy = engine.current;
      return () => {
        engineCopy?.removeAllListeners();
      };
    }, [engine]);

    useEffect(() => {
      if (remoteUsers.length > 0) {
        engine.current.setRemoteRenderMode(
          remoteUsers[0],
          RenderModeType.RenderModeHidden,
          VideoMirrorModeType.VideoMirrorModeEnabled,
        );
      }
    }, [remoteUsers]);

    /**
     * IOS Handle On Stop Screen Capture
     */
    useEffect(() => {
      if (
        Platform.OS == 'ios' &&
        sharedContentType == 'screen' &&
        !isCaptured
      ) {
        engine.current.stopScreenCapture();
        engine.current.leaveChannelEx({
          channelId,
          localUid: connection?.localUid,
        });
        joinChannel();
      }
    }, [
      Platform,
      isCaptured,
      sharedContentType,
      connection,
      channelId,
      engine,
    ]);

    /**
     * IOS Handle Start Screen Capture
     */
    useEffect(() => {
      if (Platform.OS !== 'ios') return;
      if (isCaptured) {
        engine.current.updateChannelMediaOptionsEx(
          {
            publishScreenCaptureAudio: true,
            publishScreenCaptureVideo: true,
          },
          {channelId, localUid: connection?.localUid},
        );
        engine?.current.joinChannelEx(
          token,
          {channelId, localUid: connection?.localUid},
          {
            autoSubscribeAudio: true,
            autoSubscribeVideo: false,
            publishMicrophoneTrack: true,
            publishCameraTrack: false,
            clientRoleType: ClientRoleType.ClientRoleBroadcaster,
            publishScreenCaptureAudio: true,
            publishScreenCaptureVideo: true,
          },
        );
        setSharedContentType('screen');
      }
    }, [isCaptured, Platform, engine, connection, token, channelId]);
    const joinChannel = () => {
      if (!channelId) {
        console.error('channelId is invalid');
        return;
      }
      if (uid < 0) {
        console.error('uid is invalid');
        return;
      }
      engine.current.enableVideo();

      // Start preview before joinChannel
      engine.current.startPreview();

      // start joining channel
      // 1. Users can only see each other after they join the
      // same channel successfully using the same app id.
      // 2. If app certificate is turned on at dashboard, token is needed
      // when joining channel. The channel name and uid used to calculate
      // the token has to match the ones used for channel join
      engine.current.joinChannel(token, channelId, uid, {
        // Make myself as the broadcaster to send stream to remote
        clientRoleType: ClientRoleType.ClientRoleBroadcaster,
      });
      setSharedContentType('video');
    };

    const switchCamera = () => {
      engine.current.switchCamera();
      setSwitchCamera(prev => !prev);
    };

    /**
     * Step 4: leaveChannel
     */
    const leaveChannel = async () => {
      if (sharedContentType == 'screen') {
        if (Platform.OS == 'ios') {
          await showScreenRecordPickerIOS();
        } else {
          engine.current.stopScreenCapture();
          engine.current.leaveChannelEx({
            channelId,
            localUid: connection?.localUid,
          });
        }
      }
      engine.current.leaveChannel();
      dispatch(sharePlayEnded(true));
    };

    const showScreenRecordPickerIOS = async () => {
      const handle = findNodeHandle(screenCapturePickerViewRef.current);
      await NativeModules.ScreenCapturePickerViewManager.show(handle);
    };

    const startScreenCapture = async () => {
      engine.current.leaveChannel();
      engine.current.startScreenCapture({
        captureAudio: true,
        captureVideo: true,
      });
      engine.current.startPreview(VideoSourceType.VideoSourceScreen);
      if (Platform.OS === 'ios') {
        // Show the picker view for screen share, ⚠️ only support for iOS 12+
        return showScreenRecordPickerIOS();
      } else {
        engine.current.updateChannelMediaOptionsEx(
          {
            publishScreenCaptureAudio: true,
            publishScreenCaptureVideo: true,
          },
          {channelId, localUid: connection?.localUid},
        );
        engine?.current.joinChannelEx(
          token,
          {channelId, localUid: connection?.localUid},
          {
            autoSubscribeAudio: true,
            autoSubscribeVideo: false,
            publishMicrophoneTrack: true,
            publishCameraTrack: false,
            clientRoleType: ClientRoleType.ClientRoleBroadcaster,
            publishScreenCaptureAudio: true,
            publishScreenCaptureVideo: true,
          },
        );
      }
    };

    const stopScreenCapture = () => {
      if (Platform.OS == 'ios') {
        return showScreenRecordPickerIOS();
      } else {
        engine.current.stopScreenCapture();
        engine.current.leaveChannelEx({
          channelId,
          localUid: connection?.localUid,
        });
        joinChannel();
      }
    };

Podfile :

target 'AgoraBroadcast' do
  pod 'AgoraRtcEngine_iOS', '4.3.2'
end

Also I noticed that the showRPSystemBroadcastPickerView(true) from react-native-agora makes my app crashed

The error from sentry that I got is NSInvalidArgumentException *** -[NSBundle initWithURL:]: nil URL argument

bagusaff commented 2 months ago

https://github.com/AgoraIO-Extensions/react-native-agora/assets/16956807/298b79c5-e16b-4fbc-82d4-85dfa4a2bcf4

Tried the examples ScreenShare module and got the same results.. Broadcast won't start, can anyone confirm or it's just my device ?

guoxianzhe commented 2 months ago

@bagusaff Can you tell me what does ScreenCapturePickerViewManager do? You can refer to the this example.

I can not reproduce this issue in demo, did you config signing in ScreenShare target?

bagusaff commented 2 months ago

Hi @guoxianzhe Thanks for the response!

ScreenCapturePickerViewManager is just for opening the IOS Broadcast Selector Popup

Yes I did config signing in ScreenShare, here's the screenshot

Screenshot 2024-06-13 at 20 25 08
guoxianzhe commented 2 months ago

@bagusaff Can you reinstall the app and restart your iphone then try again?

bagusaff commented 2 months ago

Yes! Restarting my Device solved the problem! I also noticed that I can't share broadcast on other app aswell before I restarted my device, Im not sure why. Thanks for helping @guoxianzhe