/* * Copyright 2020 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include "pc/rtp_transmission_manager.h" #include #include #include #include #include #include #include "absl/algorithm/container.h" #include "absl/functional/any_invocable.h" #include "absl/strings/string_view.h" #include "api/audio_options.h" #include "api/crypto/crypto_options.h" #include "api/environment/environment.h" #include "api/make_ref_counted.h" #include "api/media_stream_interface.h" #include "api/media_types.h" #include "api/peer_connection_interface.h" #include "api/rtc_error.h" #include "api/rtp_parameters.h" #include "api/rtp_receiver_interface.h" #include "api/rtp_sender_interface.h" #include "api/rtp_transceiver_direction.h" #include "api/scoped_refptr.h" #include "api/sequence_checker.h" #include "media/base/media_channel.h" #include "media/base/media_config.h" #include "media/base/media_engine.h" #include "pc/audio_rtp_receiver.h" #include "pc/channel_interface.h" #include "pc/codec_vendor.h" #include "pc/connection_context.h" #include "pc/legacy_stats_collector_interface.h" #include "pc/rtp_receiver.h" #include "pc/rtp_receiver_proxy.h" #include "pc/rtp_sender.h" #include "pc/rtp_sender_proxy.h" #include "pc/rtp_transceiver.h" #include "pc/scoped_operations_batcher.h" #include "pc/simulcast_description.h" #include "pc/usage_pattern.h" #include "pc/video_rtp_receiver.h" #include "rtc_base/checks.h" #include "rtc_base/crypto_random.h" #include "rtc_base/logging.h" #include "rtc_base/system/plan_b_only.h" namespace webrtc { namespace { const char kDefaultAudioSenderId[] = "defaulta0"; const char kDefaultVideoSenderId[] = "defaultv0"; template MediaType TrackType(T& track) { return track->kind() == MediaStreamTrackInterface::kAudioKind ? MediaType::AUDIO : MediaType::VIDEO; } std::optional GetSenderSsrc(const std::vector& infos, absl::string_view stream_id, absl::string_view sender_id) { auto it = absl::c_find_if(infos, [&](const RtpSenderInfo& info) { return info.stream_id == stream_id && info.sender_id == sender_id; }); return it == infos.end() ? std::nullopt : std::optional((*it).first_ssrc); } } // namespace RtpTransmissionManager::RtpTransmissionManager( const Environment& env, Call* call, bool is_unified_plan, ConnectionContext* context, CodecLookupHelper* codec_lookup_helper, UsagePattern* usage_pattern, PeerConnectionObserver* observer, LegacyStatsCollectorInterface* legacy_stats, absl::AnyInvocable on_negotiation_needed) : env_(env), is_unified_plan_(is_unified_plan), call_(call), context_(context), codec_lookup_helper_(codec_lookup_helper), usage_pattern_(usage_pattern), observer_(observer), legacy_stats_(legacy_stats), on_negotiation_needed_(std::move(on_negotiation_needed)), weak_ptr_factory_(this) {} void RtpTransmissionManager::Close() { closed_ = true; observer_ = nullptr; } // Implementation of SetStreamsObserver void RtpTransmissionManager::OnSetStreams() { RTC_DCHECK_RUN_ON(signaling_thread()); RTC_DCHECK(IsUnifiedPlan()); OnNegotiationNeeded(); } // Function to call back to the PeerConnection when negotiation is needed void RtpTransmissionManager::OnNegotiationNeeded() { on_negotiation_needed_(); } std::vector RtpTransmissionManager::GetDefaultHeaderExtensions(MediaType media_type) { if (media_type == MediaType::AUDIO) { return media_engine()->voice().GetRtpHeaderExtensions(&env_.field_trials()); } RTC_DCHECK_EQ(media_type, MediaType::VIDEO); return media_engine()->video().GetRtpHeaderExtensions(&env_.field_trials()); } void RtpTransmissionManager::RunWithObserver( absl::AnyInvocable task) { // NOLINT RTC_DCHECK_RUN_ON(signaling_thread()); RTC_DCHECK(observer_); std::move(task)(observer_); } PLAN_B_ONLY VoiceMediaSendChannelInterface* RtpTransmissionManager::voice_media_send_channel() const { RTC_DCHECK_RUN_ON(signaling_thread()); RTC_DCHECK(!IsUnifiedPlan()); return GetAudioTransceiver()->internal()->voice_media_send_channel(); } PLAN_B_ONLY VideoMediaSendChannelInterface* RtpTransmissionManager::video_media_send_channel() const { RTC_DCHECK_RUN_ON(signaling_thread()); RTC_DCHECK(!IsUnifiedPlan()); return GetVideoTransceiver()->internal()->video_media_send_channel(); } PLAN_B_ONLY VoiceMediaReceiveChannelInterface* RtpTransmissionManager::voice_media_receive_channel() const { RTC_DCHECK_RUN_ON(signaling_thread()); RTC_DCHECK(!IsUnifiedPlan()); return GetAudioTransceiver()->internal()->voice_media_receive_channel(); } PLAN_B_ONLY VideoMediaReceiveChannelInterface* RtpTransmissionManager::video_media_receive_channel() const { RTC_DCHECK_RUN_ON(signaling_thread()); RTC_DCHECK(!IsUnifiedPlan()); return GetVideoTransceiver()->internal()->video_media_receive_channel(); } PLAN_B_ONLY RTCErrorOr> RtpTransmissionManager::AddTrackPlanB( scoped_refptr track, const std::vector& stream_ids, const std::vector* init_send_encodings) { RTC_DCHECK_RUN_ON(signaling_thread()); RTC_DCHECK(!IsUnifiedPlan()); if (stream_ids.size() > 1u) { return LOG_ERROR(RTCError::UnsupportedOperation() << "AddTrack with more than one stream is not supported " "with Plan B semantics."); } std::vector adjusted_stream_ids; if (stream_ids.empty()) { adjusted_stream_ids.push_back(CreateRandomUuid()); } else { adjusted_stream_ids.reserve(stream_ids.size()); absl::c_copy_if(stream_ids, std::back_inserter(adjusted_stream_ids), [&](const auto& id) { return !absl::c_linear_search(adjusted_stream_ids, id); }); } MediaType media_type = TrackType(track); RtpTransceiver* transceiver = media_type == MediaType::AUDIO ? GetAudioTransceiver()->internal() : GetVideoTransceiver()->internal(); scoped_refptr> new_sender = transceiver->AddSenderPlanB(track, track->id(), adjusted_stream_ids, init_send_encodings ? *init_send_encodings : std::vector(1)); RTC_DCHECK(new_sender->internal()->stream_ids() == adjusted_stream_ids); std::optional ssrc = GetSenderSsrc(media_type == MediaType::AUDIO ? local_audio_sender_infos_ : local_video_sender_infos_, adjusted_stream_ids[0], track->id()); if (ssrc) { new_sender->internal()->SetSsrc(*ssrc); } NoteUsageEvent(media_type == MediaType::AUDIO ? UsageEvent::AUDIO_ADDED : UsageEvent::VIDEO_ADDED); return scoped_refptr(new_sender); } RTCErrorOr> RtpTransmissionManager::AddTrackUnifiedPlan( const MediaConfig& media_config, const AudioOptions& audio_options, const VideoOptions& video_options, const CryptoOptions& crypto_options, VideoBitrateAllocatorFactory* video_bitrate_allocator_factory, scoped_refptr track, const std::vector& stream_ids, const std::vector* init_send_encodings) { RTC_DCHECK(IsUnifiedPlan()); auto transceiver = FindFirstTransceiverForAddedTrack(track, init_send_encodings); if (transceiver) { RTC_LOG(LS_INFO) << "Reusing an existing " << MediaTypeToString(transceiver->media_type()) << " transceiver for AddTrack."; if (transceiver->stopping()) { return LOG_ERROR(RTCError::InvalidParameter() << "The existing transceiver is stopping."); } if (transceiver->direction() == RtpTransceiverDirection::kRecvOnly) { transceiver->internal()->set_direction( RtpTransceiverDirection::kSendRecv); } else if (transceiver->direction() == RtpTransceiverDirection::kInactive) { transceiver->internal()->set_direction( RtpTransceiverDirection::kSendOnly); } transceiver->sender()->SetTrack(track.get()); transceiver->internal()->sender_internal()->set_stream_ids(stream_ids); transceiver->internal()->set_reused_for_addtrack(true); } else { MediaType media_type = TrackType(track); RTC_LOG(LS_INFO) << "Adding " << MediaTypeToString(media_type) << " transceiver in response to a call to AddTrack."; std::string sender_id = track->id(); // Avoid creating a sender with an existing ID by generating a random ID. // This can happen if this is the second time AddTrack has created a sender // for this track. if (FindSenderById(sender_id)) { sender_id = CreateRandomUuid(); } ScopedOperationsBatcher worker_tasks(context_->worker_thread()); transceiver = CreateAndAddTransceiver( media_config, audio_options, video_options, crypto_options, video_bitrate_allocator_factory, media_type, track, stream_ids, init_send_encodings ? *init_send_encodings : std::vector(1, RtpEncodingParameters{}), /*header_extensions_to_negotiate=*/{}, /*simulcast_rejected=*/false, /*initial_simulcast_layers=*/{}, worker_tasks, sender_id, /*receiver_id=*/""); RTCError error = worker_tasks.Run(); RTC_DCHECK(error.ok()); transceiver->internal()->set_created_by_addtrack(true); transceiver->internal()->set_direction(RtpTransceiverDirection::kSendRecv); } return transceiver->sender(); } scoped_refptr> RtpTransmissionManager::CreateAndAddTransceiver( const MediaConfig& media_config, const AudioOptions& audio_options, const VideoOptions& video_options, const CryptoOptions& crypto_options, VideoBitrateAllocatorFactory* video_bitrate_allocator_factory, MediaType media_type, scoped_refptr track, const std::vector& stream_ids, const std::vector& init_send_encodings, const std::vector& header_extensions_to_negotiate, bool simulcast_rejected, const std::vector& initial_simulcast_layers, ScopedOperationsBatcher& worker_tasks, absl::string_view sender_id, absl::string_view receiver_id) { RTC_DCHECK_RUN_ON(signaling_thread()); // Ensure that the new sender does not have an ID that is already in use by // another sender. // Allow receiver IDs to conflict since those come from remote SDP (which // could be invalid, but should not cause a crash). RTC_DCHECK(!FindSenderById(sender_id)); std::vector header_extensions = std::move(header_extensions_to_negotiate); if (!env_.field_trials().IsDisabled( "WebRTC-HeaderExtensionNegotiateMemory")) { // If we have already negotiated header extensions for this type, // and it is not stopped, // reuse the negotiated state for new transceivers of the same type. for (const auto& transceiver : transceivers()->List()) { if (transceiver->media_type() == media_type && !transceiver->stopping()) { header_extensions = transceiver->GetHeaderExtensionsToNegotiate(); break; } } } if (header_extensions.empty()) { header_extensions = GetDefaultHeaderExtensions(media_type); } RtpSenderBase::SetStreamsObserver* observer = IsUnifiedPlan() ? this : nullptr; auto transceiver = RtpTransceiverProxyWithInternal::Create( signaling_thread(), make_ref_counted( env_, call_, media_config, sender_id, receiver_id, media_type, track, stream_ids, std::move(init_send_encodings), context_, codec_lookup_helper_, legacy_stats_, observer, audio_options, video_options, crypto_options, video_bitrate_allocator_factory, std::move(header_extensions), simulcast_rejected, initial_simulcast_layers, worker_tasks, [this_weak_ptr = weak_ptr_factory_.GetWeakPtr()]() { if (this_weak_ptr) { this_weak_ptr->OnNegotiationNeeded(); } })); transceivers()->Add(transceiver); NoteUsageEvent(media_type == MediaType::AUDIO ? UsageEvent::AUDIO_ADDED : UsageEvent::VIDEO_ADDED); return transceiver; } scoped_refptr> RtpTransmissionManager::FindFirstTransceiverForAddedTrack( scoped_refptr track, const std::vector* init_send_encodings) { RTC_DCHECK_RUN_ON(signaling_thread()); RTC_DCHECK(track); if (init_send_encodings != nullptr) { return nullptr; } const MediaType media_type = TrackType(track); for (auto& transceiver : transceivers()->List()) { if (!transceiver->sender()->track() && transceiver->media_type() == media_type && !transceiver->stopped() && !transceiver->internal()->has_ever_been_used_to_send()) { return transceiver; } } return nullptr; } std::vector>> RtpTransmissionManager::GetSendersInternal() const { RTC_DCHECK_RUN_ON(signaling_thread()); std::vector>> all_senders; for (const auto& transceiver : transceivers_.List()) { if (IsUnifiedPlan() && transceiver->internal()->stopped()) continue; RTC_ALLOW_PLAN_B_DEPRECATION_BEGIN() auto senders = transceiver->internal()->senders(); RTC_ALLOW_PLAN_B_DEPRECATION_END() all_senders.insert(all_senders.end(), senders.begin(), senders.end()); } return all_senders; } std::vector>> RtpTransmissionManager::GetReceiversInternal() const { RTC_DCHECK_RUN_ON(signaling_thread()); std::vector>> all_receivers; for (const auto& transceiver : transceivers_.List()) { if (IsUnifiedPlan() && transceiver->internal()->stopped()) continue; RTC_ALLOW_PLAN_B_DEPRECATION_BEGIN() auto receivers = transceiver->internal()->receivers(); RTC_ALLOW_PLAN_B_DEPRECATION_END() all_receivers.insert(all_receivers.end(), receivers.begin(), receivers.end()); } return all_receivers; } PLAN_B_ONLY scoped_refptr> RtpTransmissionManager::GetAudioTransceiver() const { RTC_DCHECK_RUN_ON(signaling_thread()); // This method only works with Plan B SDP, where there is a single // audio/video transceiver. RTC_DCHECK(!IsUnifiedPlan()); for (auto transceiver : transceivers_.List()) { if (transceiver->media_type() == MediaType::AUDIO) { return transceiver; } } RTC_DCHECK_NOTREACHED(); return nullptr; } PLAN_B_ONLY scoped_refptr> RtpTransmissionManager::GetVideoTransceiver() const { RTC_DCHECK_RUN_ON(signaling_thread()); // This method only works with Plan B SDP, where there is a single // audio/video transceiver. RTC_DCHECK(!IsUnifiedPlan()); for (auto transceiver : transceivers_.List()) { if (transceiver->media_type() == MediaType::VIDEO) { return transceiver; } } RTC_DCHECK_NOTREACHED(); return nullptr; } PLAN_B_ONLY void RtpTransmissionManager::AddTrackPlanB( MediaStreamTrackInterface* track, MediaStreamInterface* stream) { RTC_DCHECK_RUN_ON(signaling_thread()); RTC_DCHECK(track); RTC_DCHECK(stream); RTC_DCHECK(!IsUnifiedPlan()); auto sender = FindSenderForTrack(track); if (sender) { // We already have a sender for this track, so just change the stream_id // so that it's correct in the next call to CreateOffer. sender->internal()->set_stream_ids({stream->id()}); return; } // Normal case; we've never seen this track before. MediaType media_type = TrackType(track); RtpTransceiver* transceiver = media_type == MediaType::AUDIO ? GetAudioTransceiver()->internal() : GetVideoTransceiver()->internal(); scoped_refptr> new_sender = transceiver->AddSenderPlanB( scoped_refptr(track), track->id(), {stream->id()}, {}); // If the sender has already been configured in SDP, we call SetSsrc, // which will connect the sender to the underlying transport. This can // occur if a local session description that contains the ID of the sender // is set before AddStream is called. It can also occur if the local // session description is not changed and RemoveStream is called, and // later AddStream is called again with the same stream. std::optional ssrc = GetSenderSsrc(media_type == MediaType::AUDIO ? local_audio_sender_infos_ : local_video_sender_infos_, stream->id(), track->id()); if (ssrc) { new_sender->internal()->SetSsrc(*ssrc); } } // TODO(deadbeef): Don't destroy RtpSenders here; they should be kept around // indefinitely, when we have unified plan SDP. PLAN_B_ONLY void RtpTransmissionManager::RemoveTrackPlanB( MediaStreamTrackInterface* track, MediaStreamInterface* stream) { RTC_DCHECK_RUN_ON(signaling_thread()); RTC_DCHECK(!IsUnifiedPlan()); auto sender = FindSenderForTrack(track); if (!sender) { RTC_LOG(LS_WARNING) << "RtpSender for track with id " << track->id() << " doesn't exist."; return; } RtpTransceiver* transceiver = TrackType(track) == MediaType::AUDIO ? GetAudioTransceiver()->internal() : GetVideoTransceiver()->internal(); transceiver->RemoveSenderPlanB(sender.get()); } PLAN_B_ONLY void RtpTransmissionManager::CreateAudioReceiverPlanB( MediaStreamInterface* stream, const RtpSenderInfo& remote_sender_info) { RTC_DCHECK(!IsUnifiedPlan()); RTC_DCHECK(!closed_); std::vector> streams; streams.push_back(scoped_refptr(stream)); // TODO(https://crbug.com/webrtc/9480): When we remove remote_streams(), use // the constructor taking stream IDs instead. auto audio_receiver = make_ref_counted( worker_thread(), remote_sender_info.sender_id, streams, false, voice_media_receive_channel()); auto task = (remote_sender_info.sender_id == kDefaultAudioSenderId) ? audio_receiver->GetSetupForUnsignaledMediaChannel() : audio_receiver->GetSetupForMediaChannel( remote_sender_info.first_ssrc); worker_thread()->BlockingCall([&]() mutable { std::move(task)(); }); auto receiver = RtpReceiverProxyWithInternal::Create( signaling_thread(), worker_thread(), std::move(audio_receiver)); GetAudioTransceiver()->internal()->AddReceiverPlanB(receiver); RunWithObserver( [&](auto observer) { observer->OnAddTrack(receiver, streams); }); NoteUsageEvent(UsageEvent::AUDIO_ADDED); } PLAN_B_ONLY void RtpTransmissionManager::CreateVideoReceiverPlanB( MediaStreamInterface* stream, const RtpSenderInfo& remote_sender_info) { RTC_DCHECK(!IsUnifiedPlan()); RTC_DCHECK(!closed_); std::vector> streams; streams.push_back(scoped_refptr(stream)); // TODO(https://crbug.com/webrtc/9480): When we remove remote_streams(), use // the constructor taking stream IDs instead. auto video_receiver = make_ref_counted( worker_thread(), remote_sender_info.sender_id, streams, /*enable_sframe_at_owner=*/nullptr); auto task = video_receiver->GetSetupForMediaChannel( remote_sender_info.sender_id == kDefaultVideoSenderId ? std::nullopt : std::optional(remote_sender_info.first_ssrc), video_media_receive_channel()); worker_thread()->BlockingCall([&]() mutable { std::move(task)(); }); auto receiver = RtpReceiverProxyWithInternal::Create( signaling_thread(), worker_thread(), std::move(video_receiver)); GetVideoTransceiver()->internal()->AddReceiverPlanB(receiver); RunWithObserver( [&](auto observer) { observer->OnAddTrack(receiver, streams); }); NoteUsageEvent(UsageEvent::VIDEO_ADDED); } // TODO(deadbeef): Keep RtpReceivers around even if track goes away in remote // description. PLAN_B_ONLY scoped_refptr RtpTransmissionManager::RemoveAndStopReceiver( const RtpSenderInfo& remote_sender_info) { RTC_DCHECK(!IsUnifiedPlan()); auto receiver = FindReceiverById(remote_sender_info.sender_id); if (!receiver) { RTC_LOG(LS_WARNING) << "RtpReceiver for track with id " << remote_sender_info.sender_id << " doesn't exist."; return nullptr; } if (receiver->media_type() == MediaType::AUDIO) { GetAudioTransceiver()->internal()->RemoveReceiverPlanB(receiver.get()); } else { GetVideoTransceiver()->internal()->RemoveReceiverPlanB(receiver.get()); } return receiver; } PLAN_B_ONLY void RtpTransmissionManager::OnRemoteSenderAddedPlanB( const RtpSenderInfo& sender_info, MediaStreamInterface* stream, MediaType media_type) { RTC_DCHECK_RUN_ON(signaling_thread()); RTC_DCHECK(!IsUnifiedPlan()); RTC_LOG(LS_INFO) << "Creating " << MediaTypeToString(media_type) << " receiver for track_id=" << sender_info.sender_id << " and stream_id=" << sender_info.stream_id; if (media_type == MediaType::AUDIO) { CreateAudioReceiverPlanB(stream, sender_info); } else if (media_type == MediaType::VIDEO) { CreateVideoReceiverPlanB(stream, sender_info); } else { RTC_DCHECK_NOTREACHED() << "Invalid media type"; } } PLAN_B_ONLY void RtpTransmissionManager::OnRemoteSenderRemovedPlanB( const RtpSenderInfo& sender_info, MediaStreamInterface* stream, MediaType media_type) { RTC_DCHECK(!IsUnifiedPlan()); RTC_DCHECK_RUN_ON(signaling_thread()); RTC_LOG(LS_INFO) << "Removing " << MediaTypeToString(media_type) << " receiver for track_id=" << sender_info.sender_id << " and stream_id=" << sender_info.stream_id; scoped_refptr receiver; if (media_type == MediaType::AUDIO) { // When the MediaEngine audio channel is destroyed, the RemoteAudioSource // will be notified which will end the AudioRtpReceiver::track(). receiver = RemoveAndStopReceiver(sender_info); scoped_refptr audio_track = stream->FindAudioTrack(sender_info.sender_id); if (audio_track) { stream->RemoveTrack(audio_track); } } else if (media_type == MediaType::VIDEO) { // Stopping or destroying a VideoRtpReceiver will end the // VideoRtpReceiver::track(). receiver = RemoveAndStopReceiver(sender_info); scoped_refptr video_track = stream->FindVideoTrack(sender_info.sender_id); if (video_track) { // There's no guarantee the track is still available, e.g. the track may // have been removed from the stream by an application. stream->RemoveTrack(video_track); } } else { RTC_DCHECK_NOTREACHED() << "Invalid media type"; } if (receiver) { RTC_DCHECK(!closed_); RunWithObserver([&](auto observer) { observer->OnRemoveTrack(receiver); }); } } PLAN_B_ONLY void RtpTransmissionManager::OnLocalSenderAdded( const RtpSenderInfo& sender_info, MediaType media_type) { RTC_DCHECK_RUN_ON(signaling_thread()); RTC_DCHECK(!IsUnifiedPlan()); auto sender = FindSenderById(sender_info.sender_id); if (!sender) { RTC_LOG(LS_WARNING) << "An unknown RtpSender with id " << sender_info.sender_id << " has been configured in the local description."; return; } if (sender->media_type() != media_type) { RTC_LOG(LS_WARNING) << "An RtpSender has been configured in the local" " description with an unexpected media type."; return; } sender->internal()->set_stream_ids({sender_info.stream_id}); sender->internal()->SetSsrc(sender_info.first_ssrc); } PLAN_B_ONLY void RtpTransmissionManager::OnLocalSenderRemoved( const RtpSenderInfo& sender_info, MediaType media_type) { RTC_DCHECK_RUN_ON(signaling_thread()); auto sender = FindSenderById(sender_info.sender_id); if (!sender) { // This is the normal case. I.e., RemoveStream has been called and the // SessionDescriptions has been renegotiated. return; } // A sender has been removed from the SessionDescription but it's still // associated with the PeerConnection. This only occurs if the SDP doesn't // match with the calls to CreateSender, AddStream and RemoveStream. if (sender->media_type() != media_type) { RTC_LOG(LS_WARNING) << "An RtpSender has been configured in the local" " description with an unexpected media type."; return; } sender->internal()->SetSsrc(0); } std::vector* RtpTransmissionManager::GetRemoteSenderInfos( MediaType media_type) { RTC_DCHECK(media_type == MediaType::AUDIO || media_type == MediaType::VIDEO); return (media_type == MediaType::AUDIO) ? &remote_audio_sender_infos_ : &remote_video_sender_infos_; } std::vector* RtpTransmissionManager::GetLocalSenderInfos( MediaType media_type) { RTC_DCHECK(media_type == MediaType::AUDIO || media_type == MediaType::VIDEO); return (media_type == MediaType::AUDIO) ? &local_audio_sender_infos_ : &local_video_sender_infos_; } scoped_refptr> RtpTransmissionManager::FindSenderForTrack( MediaStreamTrackInterface* track) const { RTC_DCHECK_RUN_ON(signaling_thread()); for (const auto& transceiver : transceivers_.List()) { RTC_ALLOW_PLAN_B_DEPRECATION_BEGIN() for (auto sender : transceiver->internal()->senders()) { if (sender->track() == track) { return sender; } } RTC_ALLOW_PLAN_B_DEPRECATION_END() } return nullptr; } scoped_refptr> RtpTransmissionManager::FindSenderById(absl::string_view sender_id) const { RTC_DCHECK_RUN_ON(signaling_thread()); for (const auto& transceiver : transceivers_.List()) { RTC_ALLOW_PLAN_B_DEPRECATION_BEGIN(); // Under Unified Plan, senders() always has exactly one entry, // and one can use sender() not senders(). // Since this function is used both in Plan B and Unified, this is // left as-is for now. for (auto sender : transceiver->internal()->senders()) { if (sender->id() == sender_id) { return sender; } } RTC_ALLOW_PLAN_B_DEPRECATION_END(); } return nullptr; } PLAN_B_ONLY scoped_refptr> RtpTransmissionManager::FindReceiverById(absl::string_view receiver_id) const { RTC_DCHECK_RUN_ON(signaling_thread()); for (const auto& transceiver : transceivers_.List()) { for (auto receiver : transceiver->internal()->receivers()) { if (receiver->id() == receiver_id) { return receiver; } } } return nullptr; } const MediaEngineInterface* RtpTransmissionManager::media_engine() const { return context_->media_engine(); } } // namespace webrtc