3

I'm using Webrtc to share a PC's desktop (browser doesn't matter: Chrome/Firefox/Edge) with a Intel Nuc running Ubuntu 22.04.1 and Firefox 107, in the same LAN. The Nuc is used as a kiosk, and autostarts Firefox at a given URL. Web pages and Signalling (SignalR) are provided on a remote (not in the LAN) IIS server running Asp.net MVC (.net framework 4.8).

This used to work perfectly in Xubuntu 18.04 and 16.04 with Firefox 88 and even 57.

Now I get these symptoms: After booting the Nuc, Firefox autostarts, and waits for a webrtc offer (coming from the PC).

  • When the screen sharing session is initiated from a PC within a minute after the NUC booted, it all works fine.
  • When the screen sharing session is initiated from a PC after a minute or a bit longer after the NUC booted, the video isn't playing. RTC connection setup is fine, and about:webrtc shows the packages coming in.
  • After a page reload on the NUC, it all seems to work fine

I compared the about:webrtc output from a success and failure, and they look very similar appart from timestamps, guids, port numbers. The only obvious difference is the fact that the "Video Frame Statistics" part contains entries when it works while it's empty when it fails. (but packages are coming in, in both situations)

I tested this on a clean install of Ubuntu 22.04.1. I configured autoboot and autostart Firefox on the rtcTestReceive page.

This is my code: RtcTestReceive.cshtml (Runs on the NUC, the Ubuntu machine that will receive the stream)

@{
    Layout = null;
}
<html>
<head>
    <script src="~/Assets/Scripts/jquery-2.2.0.min.js"></script>
    <script src="~/Assets/Scripts/jquery.signalR-2.4.1.min.js"></script>
    <script type="text/javascript">
        var _peerConnectionId;

        var _rtcConnection = new RTCPeerConnection();
        
        _rtcConnection.onicecandidate = function (event) {
            if (event.candidate) {
                console.log("WebRTC: ICE candidate sent to connection id " + JSON.stringify(event.candidate) + ", peer: " + _peerConnectionId);
                _signalRhub.invoke("SendICECandidate", JSON.stringify(event.candidate), _peerConnectionId);
            }
        };

        // New remote media stream was added
        _rtcConnection.ontrack = function (event) {
            console.log('WebRTC: received track', event);

            $("#webrtcvideo")[0].oncanplay = function () { console.log(`video oncanplay`) };
            $("#webrtcvideo")[0].onplay = function () { console.log(`video onplay`); };
            $("#webrtcvideo")[0].onpause = function () { console.log(`video onpause`) };
            $("#webrtcvideo")[0].srcObject = event.streams[0];
            //also tried $("#webrtcvideo")[0].srcObject = new MediaStream([event.track]);
        };

        _rtcConnection.oniceconnectionstatechange = function () {
            console.log('WebRTC: RTCConnection state changed: ' + _rtcConnection.iceConnectionState);
        };

        var _signalRconnection = $.hubConnection();
        var _signalRhub = _signalRconnection.createHubProxy('rtctest');

        _signalRhub.on('StartScreenMirroring', function (sdp, peerConnectionId) {
            _peerConnectionId = peerConnectionId;
            console.log("WebRTC: ReceiveOffer connection signallingState: " + _rtcConnection.signalingState);

            _rtcConnection.setRemoteDescription(JSON.parse(sdp)).then(function () {
                _rtcConnection.createAnswer().then(function (sdp) {
                    _rtcConnection.setLocalDescription(sdp).then(function () {
                        console.log("WebRTC: Set local desc and send answer to " + peerConnectionId);
                        _signalRhub.invoke("SendAnswer", JSON.stringify(_rtcConnection.localDescription), peerConnectionId);
                    });
                })                
            });
        });

        _signalRhub.on('ReceiveICECandidate', function (data) {
            console.log('WebRTC: received ice candidate: ' + (data === null || data === undefined ? "NULL" : data));
            var candidate = new RTCIceCandidate(JSON.parse(data));
            _rtcConnection.addIceCandidate(candidate);
        });

        _signalRconnection.start().done(function () {
            console.log('WebRTC: Connected to hub with connection id:' + _signalRhub.connection.id);
        });

    </script>
</head>
<body>
    ip: <div id="ip">@ViewBag.ip</div>
    <video id="webrtcvideo" autoPlay></video>
</body>
</html>

RtcTestSend.cshtml (Runs on the sender, the PC that wants to share te screen)

@{
    Layout = null;
}
<html>
<head>
    <script src="~/Assets/Scripts/jquery-2.2.0.min.js"></script>
    <script src="~/Assets/Scripts/jquery.signalR-2.4.1.min.js"></script>
    <script type="text/javascript">
        var _queuedIceCandidates = [];
        var _peerConnectionId;

        var _rtcConnection = new RTCPeerConnection();
        _rtcConnection.onicecandidate = function (event) {
            if (event.candidate) {
                // Let's send it to our peer via SignalR
                if (_peerConnectionId !== undefined) {
                    console.log("WebRTC: ICE candidate sent to connection id " + JSON.stringify(event.candidate) + ", peer: " + _peerConnectionId);
                    _signalRhub.invoke("SendICECandidate", JSON.stringify(event.candidate), _peerConnectionId);
                } else {
                    console.log("WebRTC: queued ICE candidate: " + JSON.stringify(event.candidate));
                    _queuedIceCandidates.push(event.candidate);
                }
            }
        };

        _rtcConnection.oniceconnectionstatechange = function () {
            console.log('WebRTC: RTCConnection state changed: ' + _rtcConnection.iceConnectionState);
        };

        var _signalRconnection = $.hubConnection();
        var _signalRhub = _signalRconnection.createHubProxy('rtctest');
        _signalRhub.on('ReceiveAnswer', function (sdp, peerConnectionId) {
            _peerConnectionId = peerConnectionId;
            console.log('WebRTC: received answer from: ' + _peerConnectionId);

            _rtcConnection.setRemoteDescription(JSON.parse(sdp)).then(function () {
                _sendQueuedICECandidates();                
            });
        });

        _signalRhub.on('ReceiveICECandidate', function (data) {
            console.log('WebRTC: received ice candidate: ' + (data === null || data === undefined ? "NULL" : data));
            var candidate = new RTCIceCandidate(JSON.parse(data));
            _rtcConnection.addIceCandidate(candidate);
        });

        _signalRconnection.start().done(function () {
            console.log('WebRTC: Connected to hub with connection id:' + _signalRhub.connection.id);
        });
        
        function _sendQueuedICECandidates() {
            if (_queuedIceCandidates.length > 0 && _peerConnectionId !== undefined) {
                _queuedIceCandidates.forEach(function (candidate) {
                    console.log("WebRTC: Sending queued ICE candidate to " + _peerConnectionId + " candidate: " + JSON.stringify(candidate));
                    _signalRhub.invoke("SendICECandidate", JSON.stringify(candidate), _peerConnectionId);
                });
                _queuedIceCandidates = [];
            }
        }

        function _StartStreaming(stream) {
            console.log("WebRTC: StartStreaming stream id: " + stream.id);
            $("#webrtcvideolocal")[0].srcObject = stream;
            
            stream.getTracks().forEach(
                function (track) {
                    console.log("WebRTC: addtrack id " + track.id);
                    _rtcConnection.addTrack(track, stream);
                }
            );

            _rtcConnection.createOffer().then(function (offer) {
                _rtcConnection.setLocalDescription(offer)
                    .then(function () {
                        _signalRhub.invoke("CreateOfferToIp", JSON.stringify(offer), $("#remoteip").val());
                    });
            });
        }

        $(function () {
            $("#startRtc").on("click", function (e) {
                navigator.mediaDevices.getDisplayMedia({ video: true, audio: true })
                    .then(_StartStreaming);
            });
        });
    </script>
</head>
<body>
    remote ip: <input type="text" id="remoteip" />
    <button id="startRtc">Start rtc</button>
    <br />
    <video id="webrtcvideolocal" autoPlay></video>
</body>
</html>

SignalR Hub (runs on the remote server)

using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
using System.Net.Sockets;
using System.Net;
using System.Threading.Tasks;
using System.Web;

namespace Website
{
    [HubName("rtctest")]
    public class RtcTestHub: Hub {

        public override Task OnConnected()
        {
            string ip = HttpContext.Current.Request.UserHostAddress;
            Groups.Add(Context.ConnectionId, "ip_" + ip);
            return base.OnConnected();
        }

        public void SendICECandidate(string candidate, string peerConnection) {
            Clients.Client(peerConnection).ReceiveICECandidate(candidate);
        }
    
        public void CreateOfferToIp(string SDP, string ip) {
            Clients.Group("ip_" + ip).StartScreenMirroring(SDP, Context.ConnectionId);
        }

        public void SendAnswer(string SDP, string peerConnectionId) {
            Clients.Client(peerConnectionId).ReceiveAnswer(SDP, Context.ConnectionId);
        }

        
    }
}
fre_der
  • 83
  • 10

1 Answers1

2

I concluded this behavior is due to a bug in Firefox 96+ which is still unresolved: https://bugzilla.mozilla.org/show_bug.cgi?id=1755609

Everything works fine in Firefox 95.0.2, so I'll stick to that version.

fre_der
  • 83
  • 10