Search Results for

    Show / Hide Table of Contents

    Share Screens

    In this tutorial, you'll learn how to share screens in a conference.

    In the previous tutorial, you've learned that to stream media, we created a local media object to capture a user's local audio and video data.

    Likewise, to share a user's screen at the same time, we'll create an additional screen share local media object to capture the local user's screen. Then, we'll create an additional upstream connection to upload the screen share local media. Other participants get the screen share downstream connection the same way as the other media downstream.

    graph LR A((A)) -.->s(SFU)-->A-.screen.->s B((B))-.->s-->B C((C))-.->s-->C s-->A s-->B s-->C s-->|screen|B s-->|screen|C classDef default fill: #D8EFF9,stroke:#86CBEE;

    Prerequisites

    This tutorial requires the SFU connection app or any apps you have built earlier on top of it.

    Create Screen Share Local Media

    • CSharp
    • Android
    • iOS
    • TypeScript

    In the previous tutorial, to capture user's video with their camera, you created a CameraLocalMedia class derived from the LocalMedia class. This class overrides the CreateVideoSource method and returns a CameraSource as your video source. To support screen capture, we'll create a ScreenShareLocalMedia class derived from the LocalMedia class and override the CreateVideoSource method to return a ScreenSource as the video source.

    Paste the following code to the ScreenShareLocalMedia.cs file.

    // Local Media implementation for screen share
    class ScreenShareLocalMedia : LocalMedia
    {
        public ScreenShareLocalMedia(bool disableAudio, bool disableVideo, AecContext aecContext) 
            : base(disableAudio, disableVideo, aecContext) 
        {
            Initialize();
        }
    
        protected override VideoSource CreateVideoSource()
        {
            return new FM.LiveSwitch.WinForms.ScreenSource(3);
        }
    
        protected override ViewSink<System.Windows.Controls.Image> CreateViewSink()
        {
            return new FM.LiveSwitch.Wpf.ImageSink()
            {
                ViewScale = LayoutScale.Contain
            };
        }
    }
    

    In the previous tutorial, to capture user's video with their camera, you created a CameraLocalMedia class derived from the LocalMedia class. This class overrides the CreateVideoSource method and returns a CameraSource as your video source. To support screen capture, we'll create a ScreenShareLocalMedia class derived from the LocalMedia class and override the CreateVideoSource method to return a ScreenSource as the video source.

    Paste the following code to the ScreenShareLocalMedia.java file.

    public class ScreenShareLocalMedia extends LocalMedia<FrameLayout> {
    
        private final MediaProjectionSource projectionSource;
    
        public ScreenShareLocalMedia(MediaProjection projection, Context context, boolean disableAudio, boolean disableVideo, AecContext aecContext) {
            super(context, disableAudio, disableVideo, aecContext);
    
            this.context = context;
            projectionSource = new MediaProjectionSource(projection, context, 1);
    
            super.initialize();
        }
    
        @Override
        protected VideoSource createVideoSource() {
            return projectionSource;
        }
    
        @Override
        protected ViewSink<FrameLayout> createViewSink() {
            return new fm.liveswitch.android.OpenGLSink(context);
        }
    }
    

    In the previous tutorial, to capture user's video with their camera, you created a CameraLocalMedia class derived from the LocalMedia class. This class overrides the CreateVideoSource method and returns a CameraSource as your video source. To support screen capture, we'll create a ScreenShareLocalMedia class derived from the LocalMedia class and override the CreateVideoSource method to return a ScreenSource as the video source.

    Paste the following code to the ScreenShareLocalMedia.swift file.

    class ScreenShareLocalMedia: LocalMedia {
        override init!(disableAudio: Bool, disableVideo: Bool, aecContext: FMLiveSwitchAecContext!) {
            super.init(disableAudio: disableAudio, disableVideo: disableVideo, aecContext: aecContext)
            self.initialize()
        }
        
        override func createVideoSource() -> FMLiveSwitchVideoSource!  {
            // Change here
            return FMLiveSwitchCocoaScreenSource(frameRate: 3)
        }
        
        override func createViewSink() -> FMLiveSwitchViewSink!  {
            // Change here
            return FMLiveSwitchCocoaImageViewSink()
        }
    }
    

    You've learned that to capture a user's microphone and camera, you must create an fm.liveswitch.LocalMedia instance. To capture a user's screen instead of their camera, simply provide one additional parameter to the fm.liveswitch.LocalMedia instance.

    The additional third parameter determines whether or not to capture the user's screen. We set it and the second parameter to true to capture the user's screen. We set the first parameter to false to turn off audio.

    Paste the following code into the HelloWorldLogic class in the HelloWorldLogic.ts file:

    // Create a new local media for screen capturing.
    public localScreenMedia = new fm.liveswitch.LocalMedia(false, true, true);
    

    Start and Stop Screen Share

    To share video from the camera and video from screen capture at the same time, we need to create another upstream connection. To do so, we create a new ScreenShareLocalMedia instance and pass it into the OpenSfuUpstreamConnection function to create an SfuUpstreamConnection for the screen share media. This is similar to starting and stopping the camera local media and making an upstream connection.

    Paste the following code into the HelloWorldLogic class:

    • CSharp
    • Android
    • iOS
    • TypeScript
    private LocalMedia _ScreenShareMedia;
    private SfuUpstreamConnection _ScreenShareUpstream;
    
    public async Task ToggleScreenShare()
    {
        if (_ScreenShareMedia == null)
        {
            await StartScreenShare();
        }
    
        else if (_ScreenShareUpstream != null)
        {
            await StopScreenShare();
        }
    }
    
    public async Task StartScreenShare()
    {
        _ScreenShareMedia = new ScreenShareLocalMedia(false, false, new AecContext());
        await _ScreenShareMedia.Start();
        _ScreenShareUpstream = OpenSfuUpstreamConnection(_ScreenShareMedia);
        _LayoutManager.AddRemoteView(_ScreenShareMedia.Id, _ScreenShareMedia.View);
    }
    
    public async Task StopScreenShare()
    {
        _LayoutManager.RemoveRemoteView(_ScreenShareMedia.Id);
        await _ScreenShareUpstream.Close();
        var localMedia = await _ScreenShareMedia.Stop().AsTask();
        localMedia.Destroy();
        _ScreenShareMedia = null;
    }
    
    private MediaProjection mediaProjection;
    private ScreenShareLocalMedia screenShareMedia;
    
    public void setMediaProjection(MediaProjection mediaProjection) {
        this.mediaProjection = mediaProjection;
    }
    
    SfuUpstreamConnection screenShareUpstream;
    
    public void toggleScreenShare() {
        if (mediaProjection == null) {
            return;
        }
        if (screenShareMedia == null) {
            startScreenShare();
        } else {
            stopScreenShare();
        }
    }
    
    public void startScreenShare() {
        if (screenShareMedia == null && layoutManager != null) {
            // Create a new local media for screen capturing.
            screenShareMedia = new ScreenShareLocalMedia(mediaProjection, context, false, false, new AecContext());
    
            // Start screen capturing.
            screenShareMedia.start().then(localMedia -> {
                Log.debug("Successfully started screen share.");
    
                // Open a SFU upstream connection for screen sharing.
                screenShareUpstream = openSfuUpstreamConnection(screenShareMedia);
            }).fail(ex -> {
                Log.error("Unable to start screen share.", ex);
            });
    
            // Add view to the layout.
            layoutManager.addRemoteView(screenShareMedia.getId(), screenShareMedia.getView());
        }
    }
    
    public void stopScreenShare() {
        if (screenShareMedia != null && layoutManager != null) {
            // Close the screen sharing upstream connection.
            screenShareUpstream.close().then(object -> {
                // Stop the screen capturing and remove it from the layout.
                screenShareMedia.stop().then(localMedia -> {
                    layoutManager.removeRemoteView(localMedia.getId());
                    screenShareMedia = null;
                }).fail(ex -> {
                    Log.error("Unable to stop screen share media.", ex);
                });
            });
        }
    }
    
    
    var _screenShareMedia: ScreenShareLocalMedia?
    var _screenShareUpstream: FMLiveSwitchSfuUpstreamConnection?
    
    func toggleScreenShare() -> Void {
        if (_screenShareMedia == nil) {
            enableScreenShare()
        } else if (_screenShareUpstream != nil) {
            disableScreenShare()
        }
    }
    
    func enableScreenShare() -> Void {
        _screenShareMedia = ScreenShareLocalMedia(disableAudio: false, disableVideo: false, aecContext: nil)
        _screenShareMedia?.start()?.then(resolveActionBlock: {[weak self] (obj: Any!) in
            self?._screenShareUpstream = self!.OpenSfuUpstreamConnection(localMedia: self!._screenShareMedia!)
        })?.fail(rejectActionBlock: {(e: NSException!) in
            FMLiveSwitchLog.error(withMessage: "Unable to start screen share.", ex: e)
        })
        DispatchQueue.main.async {
            self._layoutManager?.addRemoteView(withId: self._screenShareMedia?.id(), view: self._screenShareMedia?.view())
        }
    }
    
    func disableScreenShare() -> Void {
        self._screenShareUpstream?.close()?.then(resolveActionBlock: {[weak self] (obj: Any!) in
            self?._screenShareMedia?.stop()
            self?._screenShareMedia = nil
            self?._screenShareUpstream = nil
        })?.fail(rejectActionBlock: {(e: NSException!) in
            FMLiveSwitchLog.error(withMessage: "Unable to stop screen share.", ex: e)
        })
        
        DispatchQueue.main.async {
            self._layoutManager?.removeRemoteView(withId: self._screenShareMedia?.id())
        }
    }
    
    private screenSharingUpstreamConnection: fm.liveswitch.SfuUpstreamConnection;
    
    public toggleScreenSharing(): void {
        if (this.localScreenMedia.getState() === fm.liveswitch.LocalMediaState.New
            || this.localScreenMedia.getState() === fm.liveswitch.LocalMediaState.Stopped) {
            this.startScreenSharing();
        } else {
            this.stopScreenSharing();
        }
    }
    
    private startScreenSharing(): void {
        // Start screen capturing.
        this.localScreenMedia.start()
            .then(() => {
                fm.liveswitch.Log.debug("Screen capture started.");
    
                // Open a SFU upstream connection for screen sharing.
                this.screenSharingUpstreamConnection = this.openSfuUpstreamConnection(this.localScreenMedia);
            }).
            fail(() => {
                fm.liveswitch.Log.error("Screen capture could not be started.");
            });
    
        // Add the screen sharing media to the layout.
        this.layoutManager.addRemoteMedia(this.localScreenMedia);
    }
    
    private stopScreenSharing(): void {
        // Close the screen sharing upstream connection.
        this.screenSharingUpstreamConnection.close().then(() => {
            // Stop the screen capturing and remove it from the layout.
            this.localScreenMedia.stop();
            this.layoutManager.removeRemoteMedia(this.localScreenMedia);
        });
    }
    

    Uncomment UI Components

    Now, go to the files for the UI components and uncomment the code for sharing screen.

    • CSharp
    • Android
    • iOS
    • TypeScript

    In the MainWindow.xaml.cs file, uncomment the codes between the <Screen Share> and </Screen Share> tags.

    In the ScreenShareFragment.java file, uncomment all the code that's commented out.

    The Android Fragment serves as good reference on how to set up Media Projection for screen sharing. This requires a background service similarly implemented as the BackgroundService.java file. Feel free to follow the procedure there for other projects. Note that for the background service to work, it must be defined as a service in the AndroidManifest.xml file, which the project has already done for you.

    In the ScreenshareUI.swift file, uncomment all the code that's commented out.

    In the index.ts file, uncomment the codes between the <Screen Share> and </Screen Share> tags.

    Run Your App

    Run your app in your project IDE. Click Join and then click Toggle Screen Share to share your screen. Your app UI should look similar to the following:

    • CSharp
    • Android
    • iOS
    • TypeScript

    Congratulations, you've added the screen sharing feature to your conference app!

    Go to your LiveSwitch Console's homepage, and select the Hello World Application. You should see one client, one channel, and two connections.

    Go to your LiveSwitch Cloud's Dashboard page, and select the Hello World Application. You should see one client, one channel, and two connections.

    In This Article
    Back to top Copyright © LiveSwitch Inc. All Rights Reserved.Documentation for LiveSwitch Version 1.6.2