@@ -304,7 +304,9 @@ void EnsureLibDatachannelLoggerInstalled() {
304304 });
305305}
306306
307- class NativeWebRtcPeer final : public WebRtcPeer {
307+ class NativeWebRtcPeer final
308+ : public WebRtcPeer,
309+ public std::enable_shared_from_this<NativeWebRtcPeer> {
308310public:
309311 explicit NativeWebRtcPeer (RemoteFrameReader frame_reader, PixelFormat preferred_video_format)
310312 : frame_reader_(std::move(frame_reader)),
@@ -333,124 +335,148 @@ class NativeWebRtcPeer final : public WebRtcPeer {
333335 }
334336 config.disableAutoNegotiation = true ;
335337 peer_ = std::make_shared<rtc::PeerConnection>(std::move (config));
336- peer_->onStateChange ([this ](rtc::PeerConnection::State state) {
337- const int code = static_cast <int >(state);
338- // Connecting/connected fire on every session and reconnect; only
339- // the terminal-ish transitions (disconnected/failed/closed) are
340- // worth logging at INFO. The rest goes to verbose.
341- if (code >= 3 ) {
342- std::fprintf (stdout,
343- " [INFO] remote_webrtc: peer state %s\n " ,
344- PeerStateName (code));
345- std::fflush (stdout);
346- } else {
347- VerboseLog (" [INFO] remote_webrtc: peer state %s\n " ,
348- PeerStateName (code));
349- }
350- if (state == rtc::PeerConnection::State::Connected) {
351- StartVideoPump ();
352- StartAudioPump ();
353- }
354- // Surface terminal transitions to the embedder so it can drop
355- // the per-session bookkeeping and tell the browser the
356- // desktop is gone. We deliberately ignore Disconnected
357- // because libdatachannel will either climb back to Connected
358- // or escalate to Failed on its own ICE consent timer; firing
359- // on Disconnected would tear sessions down for transient
360- // wifi blips. `peer_closed_dispatched_` keeps us idempotent
361- // because Failed -> Closed often fires both edges.
362- if (state == rtc::PeerConnection::State::Failed ||
363- state == rtc::PeerConnection::State::Closed) {
364- PeerClosedHandler handler;
365- {
366- std::lock_guard<std::mutex> lock (mu_);
367- if (peer_closed_dispatched_) return ;
368- peer_closed_dispatched_ = true ;
369- handler = peer_closed_handler_;
370- }
371- if (handler) {
372- const std::string reason =
373- state == rtc::PeerConnection::State::Failed ? " peer_failed" : " peer_closed" ;
374- try {
375- handler (reason);
376- } catch (const std::exception& e) {
377- std::fprintf (stdout,
378- " [ERROR] remote_webrtc: peer closed handler threw: %s\n " ,
379- e.what ());
380- std::fflush (stdout);
381- }
382- }
383- }
338+ }
339+
340+ void InstallCallbacks () {
341+ std::weak_ptr<NativeWebRtcPeer> weak = shared_from_this ();
342+ peer_->onStateChange ([weak](rtc::PeerConnection::State state) {
343+ if (auto self = weak.lock ()) self->HandleStateChange (state);
384344 });
385- peer_->onLocalDescription ([this ](rtc::Description description) {
386- std::lock_guard<std::mutex> lock (mu_);
387- local_type_ = description.typeString ();
388- local_sdp_ = StripLipSyncGroups (std::string (description));
389- description_ready_ = true ;
390- cv_.notify_all ();
345+ peer_->onLocalDescription ([weak](rtc::Description description) {
346+ if (auto self = weak.lock ()) self->HandleLocalDescription (std::move (description));
391347 });
392- peer_->onLocalCandidate ([this ](rtc::Candidate candidate) {
393- nlohmann::json entry = {
394- {" candidate" , std::string (candidate)},
395- {" sdpMid" , candidate.mid ()},
396- };
397- LocalIceCandidateHandler handler;
348+ peer_->onLocalCandidate ([weak](rtc::Candidate candidate) {
349+ if (auto self = weak.lock ()) self->HandleLocalCandidate (std::move (candidate));
350+ });
351+ peer_->onGatheringStateChange ([weak](rtc::PeerConnection::GatheringState state) {
352+ if (auto self = weak.lock ()) self->HandleGatheringStateChange (state);
353+ });
354+ peer_->onDataChannel ([weak](std::shared_ptr<rtc::DataChannel> channel) {
355+ if (auto self = weak.lock ()) self->AttachDataChannel (std::move (channel));
356+ });
357+ peer_->onTrack ([weak](std::shared_ptr<rtc::Track> track) {
358+ if (auto self = weak.lock ()) self->HandleTrack (std::move (track));
359+ });
360+ }
361+
362+ void HandleStateChange (rtc::PeerConnection::State state) {
363+ const int code = static_cast <int >(state);
364+ // Connecting/connected fire on every session and reconnect; only
365+ // the terminal-ish transitions (disconnected/failed/closed) are
366+ // worth logging at INFO. The rest goes to verbose.
367+ if (code >= 3 ) {
368+ std::fprintf (stdout,
369+ " [INFO] remote_webrtc: peer state %s\n " ,
370+ PeerStateName (code));
371+ std::fflush (stdout);
372+ } else {
373+ VerboseLog (" [INFO] remote_webrtc: peer state %s\n " ,
374+ PeerStateName (code));
375+ }
376+ if (state == rtc::PeerConnection::State::Connected) {
377+ StartVideoPump ();
378+ StartAudioPump ();
379+ }
380+ // Surface terminal transitions to the embedder so it can drop
381+ // the per-session bookkeeping and tell the browser the
382+ // desktop is gone. We deliberately ignore Disconnected
383+ // because libdatachannel will either climb back to Connected
384+ // or escalate to Failed on its own ICE consent timer; firing
385+ // on Disconnected would tear sessions down for transient
386+ // wifi blips. `peer_closed_dispatched_` keeps us idempotent
387+ // because Failed -> Closed often fires both edges.
388+ if (state == rtc::PeerConnection::State::Failed ||
389+ state == rtc::PeerConnection::State::Closed) {
390+ PeerClosedHandler handler;
398391 {
399392 std::lock_guard<std::mutex> lock (mu_);
400- candidates_.push_back (entry);
401- handler = local_ice_handler_;
393+ if (peer_closed_dispatched_) return ;
394+ peer_closed_dispatched_ = true ;
395+ handler = peer_closed_handler_;
402396 }
403- // Trickle every host-side candidate to the embedder so the
404- // browser can start probing as soon as gathering produces
405- // host / srflx entries, instead of having to wait for the
406- // initial answer's `candidates[]` (which we cap at a short
407- // gathering window so STUN-blackholed networks don't stall
408- // session creation).
409- //
410- // Trickle frames may legitimately reach the browser before
411- // the answer SDP - libdatachannel's worker can fire
412- // onLocalCandidate before our caller has had a chance to
413- // SendJson the answer envelope. That's the standard
414- // signaling race every WebRTC client is expected to
415- // handle by queueing addIceCandidate calls until
416- // setRemoteDescription resolves; the browser-side
417- // RemoteDesktopPanel does exactly that, so we fire as
418- // soon as we have a candidate.
419397 if (handler) {
398+ const std::string reason =
399+ state == rtc::PeerConnection::State::Failed ? " peer_failed" : " peer_closed" ;
420400 try {
421- handler (std::move (entry) );
401+ handler (reason );
422402 } catch (const std::exception& e) {
423403 std::fprintf (stdout,
424- " [ERROR] remote_webrtc: local ice handler threw: %s\n " ,
404+ " [ERROR] remote_webrtc: peer closed handler threw: %s\n " ,
425405 e.what ());
426406 std::fflush (stdout);
427407 }
428408 }
429- });
430- peer_->onGatheringStateChange ([this ](rtc::PeerConnection::GatheringState state) {
431- if (state == rtc::PeerConnection::GatheringState::Complete) {
432- std::lock_guard<std::mutex> lock (mu_);
433- gathering_complete_ = true ;
434- cv_.notify_all ();
435- }
436- });
437- peer_->onDataChannel ([this ](std::shared_ptr<rtc::DataChannel> channel) {
438- AttachDataChannel (std::move (channel));
439- });
440- peer_->onTrack ([this ](std::shared_ptr<rtc::Track> track) {
441- if (!track) return ;
442- const auto description = track->description ();
443- VerboseLog (" [INFO] remote_webrtc: negotiated track type=%s mid=%s\n " ,
444- description.type ().c_str (),
445- description.mid ().c_str ());
446- if (description.type () == " video" ) {
447- ConfigureVideoTrack (std::move (track));
448- } else if (description.type () == " audio" ) {
449- ConfigureAudioTrack (std::move (track));
450- } else {
451- track->close ();
409+ }
410+ }
411+
412+ void HandleLocalDescription (rtc::Description description) {
413+ std::lock_guard<std::mutex> lock (mu_);
414+ local_type_ = description.typeString ();
415+ local_sdp_ = StripLipSyncGroups (std::string (description));
416+ description_ready_ = true ;
417+ cv_.notify_all ();
418+ }
419+
420+ void HandleLocalCandidate (rtc::Candidate candidate) {
421+ nlohmann::json entry = {
422+ {" candidate" , std::string (candidate)},
423+ {" sdpMid" , candidate.mid ()},
424+ };
425+ LocalIceCandidateHandler handler;
426+ {
427+ std::lock_guard<std::mutex> lock (mu_);
428+ candidates_.push_back (entry);
429+ handler = local_ice_handler_;
430+ }
431+ // Trickle every host-side candidate to the embedder so the
432+ // browser can start probing as soon as gathering produces
433+ // host / srflx entries, instead of having to wait for the
434+ // initial answer's `candidates[]` (which we cap at a short
435+ // gathering window so STUN-blackholed networks don't stall
436+ // session creation).
437+ //
438+ // Trickle frames may legitimately reach the browser before
439+ // the answer SDP - libdatachannel's worker can fire
440+ // onLocalCandidate before our caller has had a chance to
441+ // SendJson the answer envelope. That's the standard
442+ // signaling race every WebRTC client is expected to
443+ // handle by queueing addIceCandidate calls until
444+ // setRemoteDescription resolves; the browser-side
445+ // RemoteDesktopPanel does exactly that, so we fire as
446+ // soon as we have a candidate.
447+ if (handler) {
448+ try {
449+ handler (std::move (entry));
450+ } catch (const std::exception& e) {
451+ std::fprintf (stdout,
452+ " [ERROR] remote_webrtc: local ice handler threw: %s\n " ,
453+ e.what ());
454+ std::fflush (stdout);
452455 }
453- });
456+ }
457+ }
458+
459+ void HandleGatheringStateChange (rtc::PeerConnection::GatheringState state) {
460+ if (state == rtc::PeerConnection::GatheringState::Complete) {
461+ std::lock_guard<std::mutex> lock (mu_);
462+ gathering_complete_ = true ;
463+ cv_.notify_all ();
464+ }
465+ }
466+
467+ void HandleTrack (std::shared_ptr<rtc::Track> track) {
468+ if (!track) return ;
469+ const auto description = track->description ();
470+ VerboseLog (" [INFO] remote_webrtc: negotiated track type=%s mid=%s\n " ,
471+ description.type ().c_str (),
472+ description.mid ().c_str ());
473+ if (description.type () == " video" ) {
474+ ConfigureVideoTrack (std::move (track));
475+ } else if (description.type () == " audio" ) {
476+ ConfigureAudioTrack (std::move (track));
477+ } else {
478+ track->close ();
479+ }
454480 }
455481
456482 ~NativeWebRtcPeer () override {
@@ -669,22 +695,27 @@ class NativeWebRtcPeer final : public WebRtcPeer {
669695 // every session, so this is 4 lines per reconnect — verbose only.
670696 VerboseLog (" [INFO] remote_webrtc: data channel attached label=%s\n " ,
671697 label.c_str ());
672- std::weak_ptr<rtc::DataChannel> weak = channel;
698+ std::weak_ptr<NativeWebRtcPeer> self_weak = weak_from_this ();
699+ std::weak_ptr<rtc::DataChannel> channel_weak = channel;
673700 channel->onMessage (
674- [this , weak, label](rtc::binary /* data*/ ) {
701+ [self_weak, channel_weak, label](rtc::binary /* data*/ ) {
702+ if (!self_weak.lock () || channel_weak.expired ()) return ;
675703 std::fprintf (stdout,
676704 " [WARN] remote_webrtc: dropping binary frame on dc=%s "
677705 " (only JSON text messages are supported)\n " ,
678706 label.c_str ());
679707 std::fflush (stdout);
680708 },
681- [this , label](std::string text) {
682- DispatchDataChannelText (label, std::move (text));
709+ [self_weak, label](std::string text) {
710+ if (auto self = self_weak.lock ()) {
711+ self->DispatchDataChannelText (label, std::move (text));
712+ }
683713 });
684- channel->onOpen ([this , label] {
685- DispatchDataChannelOpen (label);
714+ channel->onOpen ([self_weak , label] {
715+ if ( auto self = self_weak. lock ()) self-> DispatchDataChannelOpen (label);
686716 });
687- channel->onClosed ([this , label] {
717+ channel->onClosed ([self_weak, label] {
718+ if (!self_weak.lock ()) return ;
688719 VerboseLog (" [INFO] remote_webrtc: data channel closed label=%s\n " ,
689720 label.c_str ());
690721 });
@@ -874,8 +905,11 @@ class NativeWebRtcPeer final : public WebRtcPeer {
874905 // estimation via LSR/DLSR is gone for video; ICE candidate-pair RTT
875906 // (browser getStats) is unaffected.
876907 packetizer->addToChain (std::make_shared<rtc::RtcpNackResponder>());
877- packetizer->addToChain (std::make_shared<rtc::PliHandler>([this ]() {
878- force_keyframe_requested_ = true ;
908+ std::weak_ptr<NativeWebRtcPeer> weak = weak_from_this ();
909+ packetizer->addToChain (std::make_shared<rtc::PliHandler>([weak]() {
910+ auto self = weak.lock ();
911+ if (!self) return ;
912+ self->force_keyframe_requested_ = true ;
879913 // Receivers fire PLI on every key frame loss / freeze recovery,
880914 // sometimes several times per second. Default to verbose; even
881915 // verbose users get rate-limited info aggregated below by the
@@ -888,9 +922,11 @@ class NativeWebRtcPeer final : public WebRtcPeer {
888922 video_track_->mid ().c_str (),
889923 color_space_id,
890924 playout_delay_id);
891- video_track_->onOpen ([this ]() {
925+ video_track_->onOpen ([weak]() {
926+ auto self = weak.lock ();
927+ if (!self) return ;
892928 VerboseLog (" [INFO] remote_webrtc: video track opened\n " );
893- StartVideoPump ();
929+ self-> StartVideoPump ();
894930 });
895931 }
896932
@@ -909,9 +945,12 @@ class NativeWebRtcPeer final : public WebRtcPeer {
909945 VerboseLog (" [INFO] remote_webrtc: audio payload type=%u mid=%s\n " ,
910946 payload_type,
911947 audio_track_->mid ().c_str ());
912- audio_track_->onOpen ([this ]() {
948+ std::weak_ptr<NativeWebRtcPeer> weak = weak_from_this ();
949+ audio_track_->onOpen ([weak]() {
950+ auto self = weak.lock ();
951+ if (!self) return ;
913952 VerboseLog (" [INFO] remote_webrtc: audio track opened\n " );
914- StartAudioPump ();
953+ self-> StartAudioPump ();
915954 });
916955 }
917956
@@ -1546,9 +1585,11 @@ class NativeWebRtcPeer final : public WebRtcPeer {
15461585std::shared_ptr<WebRtcPeer> CreateWebRtcPeer (
15471586 RemoteFrameReader frame_reader,
15481587 PixelFormat preferred_video_format) {
1549- return std::make_shared<NativeWebRtcPeer>(
1588+ auto peer = std::make_shared<NativeWebRtcPeer>(
15501589 std::move (frame_reader),
15511590 preferred_video_format);
1591+ peer->InstallCallbacks ();
1592+ return peer;
15521593}
15531594
15541595bool NativeWebRtcAvailable () {
0 commit comments