Introduce SdpOfferAnswerHandler::RemoteDescriptionOperation.

This is an operation specific subclass of SdpOfferAnswerHandler that in
this first step, takes over the implementation details that before this
CL were implemented in SdpOfferAnswerHandler::DoSetRemoteDescription.

This CL does not change the behavior of the implementation but it does
break up DoSetRemoteDescription into smaller methods and moves the state
related to the SRD operation, into a class that in upcoming steps can
be passed around asynchronously as needed, which will allow us to avoid
blocking threads.

Bug: webrtc:13540
Change-Id: Id2002d2390a4a13725f5967df5b82064b37c7490
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/244980
Reviewed-by: Henrik Boström <hbos@webrtc.org>
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Commit-Queue: Tomas Gunnarsson <tommi@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#35669}
This commit is contained in:
Tomas Gunnarsson 2022-01-12 13:11:04 +01:00 committed by WebRTC LUCI CQ
parent f5770a0198
commit 1c7c09bcfa
2 changed files with 262 additions and 139 deletions

View File

@ -747,6 +747,176 @@ bool ContentHasHeaderExtension(const cricket::ContentInfo& content_info,
} // namespace
// This class stores state related to a SetRemoteDescription operation, captures
// and reports potential errors that migth occur and makes sure to notify the
// observer of the operation and the operations chain of completion.
class SdpOfferAnswerHandler::RemoteDescriptionOperation {
public:
RemoteDescriptionOperation(
SdpOfferAnswerHandler* handler,
std::unique_ptr<SessionDescriptionInterface> desc,
rtc::scoped_refptr<SetRemoteDescriptionObserverInterface> observer,
std::function<void()> operations_chain_callback)
: handler_(handler),
desc_(std::move(desc)),
observer_(std::move(observer)),
operations_chain_callback_(std::move(operations_chain_callback)) {
if (!desc_) {
InvalidParam("SessionDescription is NULL.");
} else {
type_ = desc_->GetType();
}
}
~RemoteDescriptionOperation() {
RTC_DCHECK_RUN_ON(handler_->signaling_thread());
SignalCompletion();
operations_chain_callback_();
}
bool ok() const { return error_.ok(); }
// Notifies the observer that the operation is complete and releases the
// reference to the observer.
void SignalCompletion() {
if (observer_) {
observer_->OnSetRemoteDescriptionComplete(error_);
observer_ = nullptr; // Only fire the notification once.
}
}
// If a session error has occurred the PeerConnection is in a possibly
// inconsistent state so fail right away.
bool HaveSessionError() {
RTC_DCHECK(ok());
if (handler_->session_error() == SessionError::kNone)
return false;
SetError(RTCErrorType::INTERNAL_ERROR, handler_->GetSessionErrorMsg());
return true;
}
// Returns true if the operation was a rollback operation. If this function
// returns true, the caller should consider the operation complete. Otherwise
// proceed to the next step.
bool MaybeRollback() {
RTC_DCHECK_RUN_ON(handler_->signaling_thread());
RTC_DCHECK(ok());
if (type_ != SdpType::kRollback) {
// Check if we can do an implicit rollback.
if (type_ == SdpType::kOffer && handler_->IsUnifiedPlan() &&
handler_->pc_->configuration()->enable_implicit_rollback &&
handler_->signaling_state() ==
PeerConnectionInterface::kHaveLocalOffer) {
handler_->Rollback(type_);
}
return false;
}
if (handler_->IsUnifiedPlan()) {
error_ = handler_->Rollback(type_);
} else if (type_ == SdpType::kRollback) {
Unsupported("Rollback not supported in Plan B");
}
return true;
}
// Report to UMA the format of the received offer or answer.
void ReportOfferAnswerUma() {
RTC_DCHECK(ok());
if (type_ == SdpType::kOffer || type_ == SdpType::kAnswer) {
handler_->pc_->ReportSdpFormatReceived(*desc_.get());
handler_->pc_->ReportSdpBundleUsage(*desc_.get());
}
}
// Checks if the session description for the operation is valid. If not, the
// function captures error information and returns false. Note that if the
// return value is false, the operation should be considered done.
bool IsDescriptionValid() {
RTC_DCHECK_RUN_ON(handler_->signaling_thread());
RTC_DCHECK(ok());
RTC_DCHECK(bundle_groups_by_mid_.empty()) << "Already called?";
bundle_groups_by_mid_ = GetBundleGroupsByMid(description());
error_ = handler_->ValidateSessionDescription(
desc_.get(), cricket::CS_REMOTE, bundle_groups_by_mid_);
if (!error_.ok()) {
std::string error_message =
GetSetDescriptionErrorMessage(cricket::CS_REMOTE, type_, error_);
RTC_LOG(LS_ERROR) << error_message;
error_.set_message(std::move(error_message));
return false;
}
return true;
}
// TODO(tommi): Instead of calling `handler_->ApplyRemoteDescription` here,
// pass the ownership of `this` to that method and break down the steps
// currently implemented in that function into smaller methods implemented
// by this class. Once we have all of this broken down, we can start avoiding
// the embedded calls to Invoke() and apply the description asynchronously.
bool ApplyRemoteDescription() {
RTC_DCHECK_RUN_ON(handler_->signaling_thread());
RTC_DCHECK(ok());
// TODO(tommi): It's not ideal to move desc_ ownership.
error_ = handler_->ApplyRemoteDescription(std::move(desc_),
bundle_groups_by_mid());
// `desc` may be destroyed at this point.
if (!error_.ok()) {
// If ApplyRemoteDescription fails, the PeerConnection could be in an
// inconsistent state, so act conservatively here and set the session
// error so that future calls to SetLocalDescription/SetRemoteDescription
// fail.
handler_->SetSessionError(SessionError::kContent, error_.message());
std::string error_message =
GetSetDescriptionErrorMessage(cricket::CS_REMOTE, type_, error_);
RTC_LOG(LS_ERROR) << error_message;
error_.set_message(std::move(error_message));
return false;
}
return true;
}
// Convenience getter for desc_->GetType().
SdpType type() const { return type_; }
cricket::SessionDescription* description() { return desc_->description(); }
// Returns a reference to a cached map of bundle groups ordered by mid.
// Note that this will only be valid after a successful call to
// `IsDescriptionValid`.
const std::map<std::string, const cricket::ContentGroup*>&
bundle_groups_by_mid() const {
RTC_DCHECK(ok());
return bundle_groups_by_mid_;
}
private:
// Convenience methods for populating the embedded `error_` object.
void Unsupported(std::string message) {
SetError(RTCErrorType::UNSUPPORTED_OPERATION, std::move(message));
}
void InvalidParam(std::string message) {
SetError(RTCErrorType::INVALID_PARAMETER, std::move(message));
}
void SetError(RTCErrorType type, std::string message) {
RTC_DCHECK(ok()) << "Overwriting an existing error?";
error_ = RTCError(type, std::move(message));
}
SdpOfferAnswerHandler* const handler_;
std::unique_ptr<SessionDescriptionInterface> desc_;
rtc::scoped_refptr<SetRemoteDescriptionObserverInterface> observer_;
std::function<void()> operations_chain_callback_;
RTCError error_ = RTCError::OK();
std::map<std::string, const cricket::ContentGroup*> bundle_groups_by_mid_;
SdpType type_;
};
// Used by parameterless SetLocalDescription() to create an offer or answer.
// Upon completion of creating the session description, SetLocalDescription() is
// invoked with the result.
@ -1501,15 +1671,11 @@ void SdpOfferAnswerHandler::SetRemoteDescription(
// SetSessionDescriptionObserverAdapter takes care of making sure the
// `observer_refptr` is invoked in a posted message.
this_weak_ptr->DoSetRemoteDescription(
std::move(desc),
rtc::make_ref_counted<SetSessionDescriptionObserverAdapter>(
this_weak_ptr, observer_refptr));
// For backwards-compatability reasons, we declare the operation as
// completed here (rather than in a post), so that the operation chain
// is not blocked by this operation when the observer is invoked. This
// allows the observer to trigger subsequent offer/answer operations
// synchronously if the operation chain is now empty.
operations_chain_callback();
std::make_unique<RemoteDescriptionOperation>(
this_weak_ptr.get(), std::move(desc),
rtc::make_ref_counted<SetSessionDescriptionObserverAdapter>(
this_weak_ptr, observer_refptr),
std::move(operations_chain_callback)));
});
}
@ -1524,6 +1690,12 @@ void SdpOfferAnswerHandler::SetRemoteDescription(
[this_weak_ptr = weak_ptr_factory_.GetWeakPtr(), observer,
desc = std::move(desc)](
std::function<void()> operations_chain_callback) mutable {
if (!observer) {
RTC_DLOG(LS_ERROR) << "SetRemoteDescription - observer is NULL.";
operations_chain_callback();
return;
}
// Abort early if `this_weak_ptr` is no longer valid.
if (!this_weak_ptr) {
observer->OnSetRemoteDescriptionComplete(RTCError(
@ -1532,12 +1704,11 @@ void SdpOfferAnswerHandler::SetRemoteDescription(
operations_chain_callback();
return;
}
this_weak_ptr->DoSetRemoteDescription(std::move(desc),
std::move(observer));
// DoSetRemoteDescription() is implemented as a synchronous operation.
// The `observer` will already have been informed that it completed, and
// we can mark this operation as complete without any loose ends.
operations_chain_callback();
this_weak_ptr->DoSetRemoteDescription(
std::make_unique<RemoteDescriptionOperation>(
this_weak_ptr.get(), std::move(desc), std::move(observer),
std::move(operations_chain_callback)));
});
}
@ -1702,10 +1873,13 @@ RTCError SdpOfferAnswerHandler::ApplyRemoteDescription(
GetFirstVideoContent(remote_description()->description()), video_desc);
}
if (type == SdpType::kAnswer &&
local_ice_credentials_to_replace_->SatisfiesIceRestart(
*current_local_description_)) {
local_ice_credentials_to_replace_->ClearIceCredentials();
if (type == SdpType::kAnswer) {
if (local_ice_credentials_to_replace_->SatisfiesIceRestart(
*current_local_description_)) {
local_ice_credentials_to_replace_->ClearIceCredentials();
}
RemoveStoppedTransceivers();
}
return RTCError::OK();
@ -2131,96 +2305,41 @@ void SdpOfferAnswerHandler::DoCreateAnswer(
}
void SdpOfferAnswerHandler::DoSetRemoteDescription(
std::unique_ptr<SessionDescriptionInterface> desc,
rtc::scoped_refptr<SetRemoteDescriptionObserverInterface> observer) {
std::unique_ptr<RemoteDescriptionOperation> operation) {
RTC_DCHECK_RUN_ON(signaling_thread());
TRACE_EVENT0("webrtc", "SdpOfferAnswerHandler::DoSetRemoteDescription");
if (!observer) {
RTC_LOG(LS_ERROR) << "SetRemoteDescription - observer is NULL.";
if (!operation->ok())
return;
}
if (!desc) {
observer->OnSetRemoteDescriptionComplete(RTCError(
RTCErrorType::INVALID_PARAMETER, "SessionDescription is NULL."));
if (operation->HaveSessionError())
return;
}
// If a session error has occurred the PeerConnection is in a possibly
// inconsistent state so fail right away.
if (session_error() != SessionError::kNone) {
std::string error_message = GetSessionErrorMsg();
RTC_LOG(LS_ERROR) << "SetRemoteDescription: " << error_message;
observer->OnSetRemoteDescriptionComplete(
RTCError(RTCErrorType::INTERNAL_ERROR, std::move(error_message)));
if (operation->MaybeRollback())
return;
}
if (IsUnifiedPlan()) {
if (pc_->configuration()->enable_implicit_rollback) {
if (desc->GetType() == SdpType::kOffer &&
signaling_state() == PeerConnectionInterface::kHaveLocalOffer) {
Rollback(desc->GetType());
}
}
// Explicit rollback.
if (desc->GetType() == SdpType::kRollback) {
observer->OnSetRemoteDescriptionComplete(Rollback(desc->GetType()));
return;
}
} else if (desc->GetType() == SdpType::kRollback) {
observer->OnSetRemoteDescriptionComplete(
RTCError(RTCErrorType::UNSUPPORTED_OPERATION,
"Rollback not supported in Plan B"));
operation->ReportOfferAnswerUma();
// Handle remote descriptions missing a=mid lines for interop with legacy
// end points.
FillInMissingRemoteMids(operation->description());
if (!operation->IsDescriptionValid())
return;
}
if (desc->GetType() == SdpType::kOffer ||
desc->GetType() == SdpType::kAnswer) {
// Report to UMA the format of the received offer or answer.
pc_->ReportSdpFormatReceived(*desc);
pc_->ReportSdpBundleUsage(*desc);
}
// Handle remote descriptions missing a=mid lines for interop with legacy end
// points.
FillInMissingRemoteMids(desc->description());
std::map<std::string, const cricket::ContentGroup*> bundle_groups_by_mid =
GetBundleGroupsByMid(desc->description());
RTCError error = ValidateSessionDescription(desc.get(), cricket::CS_REMOTE,
bundle_groups_by_mid);
if (!error.ok()) {
std::string error_message = GetSetDescriptionErrorMessage(
cricket::CS_REMOTE, desc->GetType(), error);
RTC_LOG(LS_ERROR) << error_message;
observer->OnSetRemoteDescriptionComplete(
RTCError(error.type(), std::move(error_message)));
if (!operation->ApplyRemoteDescription())
return;
}
// Grab the description type before moving ownership to
// ApplyRemoteDescription, which may destroy it before returning.
const SdpType type = desc->GetType();
// Consider the operation complete at this point.
operation->SignalCompletion();
error = ApplyRemoteDescription(std::move(desc), bundle_groups_by_mid);
// `desc` may be destroyed at this point.
SetRemoteDescriptionPostProcess(operation->type() == SdpType::kAnswer);
}
if (!error.ok()) {
// If ApplyRemoteDescription fails, the PeerConnection could be in an
// inconsistent state, so act conservatively here and set the session error
// so that future calls to SetLocalDescription/SetRemoteDescription fail.
SetSessionError(SessionError::kContent, error.message());
std::string error_message =
GetSetDescriptionErrorMessage(cricket::CS_REMOTE, type, error);
RTC_LOG(LS_ERROR) << error_message;
observer->OnSetRemoteDescriptionComplete(
RTCError(error.type(), std::move(error_message)));
return;
}
// Called after a DoSetRemoteDescription operation completes.
void SdpOfferAnswerHandler::SetRemoteDescriptionPostProcess(bool was_answer) {
RTC_DCHECK(remote_description());
if (type == SdpType::kAnswer) {
RemoveStoppedTransceivers();
if (was_answer) {
// TODO(deadbeef): We already had to hop to the network thread for
// MaybeStartGathering...
pc_->network_thread()->Invoke<void>(
@ -2229,12 +2348,11 @@ void SdpOfferAnswerHandler::DoSetRemoteDescription(
ReportNegotiatedSdpSemantics(*remote_description());
}
observer->OnSetRemoteDescriptionComplete(RTCError::OK());
pc_->NoteUsageEvent(UsageEvent::SET_REMOTE_DESCRIPTION_SUCCEEDED);
// Check if negotiation is needed. We must do this after informing the
// observer that SetRemoteDescription() has completed to ensure negotiation is
// not needed prior to the promise resolving.
// observer that SetRemoteDescription() has completed to ensure negotiation
// is not needed prior to the promise resolving.
if (IsUnifiedPlan()) {
bool was_negotiation_needed = is_negotiation_needed_;
UpdateNegotiationNeeded();
@ -2279,10 +2397,10 @@ void SdpOfferAnswerHandler::SetAssociatedRemoteStreams(
}
std::vector<rtc::scoped_refptr<MediaStreamInterface>> previous_streams =
receiver->streams();
// SetStreams() will add/remove the receiver's track to/from the streams. This
// differs from the spec - the spec uses an "addList" and "removeList" to
// update the stream-track relationships in a later step. We do this earlier,
// changing the order of things, but the end-result is the same.
// SetStreams() will add/remove the receiver's track to/from the streams.
// This differs from the spec - the spec uses an "addList" and "removeList"
// to update the stream-track relationships in a later step. We do this
// earlier, changing the order of things, but the end-result is the same.
// TODO(hbos): When we remove remote_streams(), use set_stream_ids()
// instead. https://crbug.com/webrtc/9480
receiver->SetStreams(media_streams);
@ -2293,8 +2411,8 @@ bool SdpOfferAnswerHandler::AddIceCandidate(
const IceCandidateInterface* ice_candidate) {
const AddIceCandidateResult result = AddIceCandidateInternal(ice_candidate);
NoteAddIceCandidateResult(result);
// If the return value is kAddIceCandidateFailNotReady, the candidate has been
// added, although not 'ready', but that's a success.
// If the return value is kAddIceCandidateFailNotReady, the candidate has
// been added, although not 'ready', but that's a success.
return result == kAddIceCandidateSuccess ||
result == kAddIceCandidateFailNotReady;
}
@ -2350,9 +2468,9 @@ void SdpOfferAnswerHandler::AddIceCandidate(
std::function<void(RTCError)> callback) {
TRACE_EVENT0("webrtc", "SdpOfferAnswerHandler::AddIceCandidate");
RTC_DCHECK_RUN_ON(signaling_thread());
// Chain this operation. If asynchronous operations are pending on the chain,
// this operation will be queued to be invoked, otherwise the contents of the
// lambda will execute immediately.
// Chain this operation. If asynchronous operations are pending on the
// chain, this operation will be queued to be invoked, otherwise the
// contents of the lambda will execute immediately.
operations_chain_->ChainOperation(
[this_weak_ptr = weak_ptr_factory_.GetWeakPtr(),
candidate = std::move(candidate), callback = std::move(callback)](
@ -2501,8 +2619,8 @@ RTCError SdpOfferAnswerHandler::UpdateSessionState(
bundle_groups_by_mid) {
RTC_DCHECK_RUN_ON(signaling_thread());
// If there's already a pending error then no state transition should happen.
// But all call-sites should be verifying this before calling us!
// If there's already a pending error then no state transition should
// happen. But all call-sites should be verifying this before calling us!
RTC_DCHECK(session_error() == SessionError::kNone);
// If this is answer-ish we're ready to let media flow.
@ -2548,10 +2666,10 @@ bool SdpOfferAnswerHandler::ShouldFireNegotiationNeededEvent(
// one obsolete.
if (!operations_chain_->IsEmpty()) {
// Since we just suppressed an event that would have been fired, if
// negotiation is still needed by the time the chain becomes empty again, we
// must make sure to generate another event if negotiation is needed then.
// This happens when `is_negotiation_needed_` goes from false to true, so we
// set it to false until UpdateNegotiationNeeded() is called.
// negotiation is still needed by the time the chain becomes empty again,
// we must make sure to generate another event if negotiation is needed
// then. This happens when `is_negotiation_needed_` goes from false to
// true, so we set it to false until UpdateNegotiationNeeded() is called.
is_negotiation_needed_ = false;
update_negotiation_needed_on_empty_chain_ = true;
return false;
@ -2777,8 +2895,8 @@ RTCError SdpOfferAnswerHandler::Rollback(SdpType desc_type) {
pc_->Observer()->OnRemoveStream(stream);
}
// The assumption is that in case of implicit rollback UpdateNegotiationNeeded
// gets called in SetRemoteDescription.
// The assumption is that in case of implicit rollback
// UpdateNegotiationNeeded gets called in SetRemoteDescription.
if (desc_type == SdpType::kRollback) {
UpdateNegotiationNeeded();
if (is_negotiation_needed_) {
@ -2800,9 +2918,9 @@ void SdpOfferAnswerHandler::OnOperationsChainEmpty() {
if (pc_->IsClosed() || !update_negotiation_needed_on_empty_chain_)
return;
update_negotiation_needed_on_empty_chain_ = false;
// Firing when chain is empty is only supported in Unified Plan to avoid Plan
// B regressions. (In Plan B, onnegotiationneeded is already broken anyway, so
// firing it even more might just be confusing.)
// Firing when chain is empty is only supported in Unified Plan to avoid
// Plan B regressions. (In Plan B, onnegotiationneeded is already broken
// anyway, so firing it even more might just be confusing.)
if (IsUnifiedPlan()) {
UpdateNegotiationNeeded();
}
@ -2844,8 +2962,8 @@ void SdpOfferAnswerHandler::UpdateNegotiationNeeded() {
}
// In the spec, a task is queued here to run the following steps - this is
// meant to ensure we do not fire onnegotiationneeded prematurely if multiple
// changes are being made at once. In order to support Chromium's
// meant to ensure we do not fire onnegotiationneeded prematurely if
// multiple changes are being made at once. In order to support Chromium's
// implementation where the JavaScript representation of the PeerConnection
// lives on a separate thread though, the queuing of a task is instead
// performed by the PeerConnectionObserver posting from the signaling thread
@ -2868,8 +2986,8 @@ void SdpOfferAnswerHandler::UpdateNegotiationNeeded() {
// "stable", as part of the steps for setting an RTCSessionDescription.
// If the result of checking if negotiation is needed is false, clear the
// negotiation-needed flag by setting connection's [[NegotiationNeeded]] slot
// to false, and abort these steps.
// negotiation-needed flag by setting connection's [[NegotiationNeeded]]
// slot to false, and abort these steps.
bool is_negotiation_needed = CheckIfNegotiationIsNeeded();
if (!is_negotiation_needed) {
is_negotiation_needed_ = false;
@ -2892,16 +3010,16 @@ void SdpOfferAnswerHandler::UpdateNegotiationNeeded() {
// If connection's [[NegotiationNeeded]] slot is false, abort these steps.
// Fire an event named negotiationneeded at connection.
pc_->Observer()->OnRenegotiationNeeded();
// Fire the spec-compliant version; when ShouldFireNegotiationNeededEvent() is
// used in the task queued by the observer, this event will only fire when the
// chain is empty.
// Fire the spec-compliant version; when ShouldFireNegotiationNeededEvent()
// is used in the task queued by the observer, this event will only fire
// when the chain is empty.
GenerateNegotiationNeededEvent();
}
bool SdpOfferAnswerHandler::CheckIfNegotiationIsNeeded() {
RTC_DCHECK_RUN_ON(signaling_thread());
// 1. If any implementation-specific negotiation is required, as described at
// the start of this section, return true.
// 1. If any implementation-specific negotiation is required, as described
// at the start of this section, return true.
// 2. If connection.[[LocalIceCredentialsToReplace]] is not empty, return
// true.
@ -3048,9 +3166,8 @@ RTCError SdpOfferAnswerHandler::ValidateSessionDescription(
cricket::ContentSource source,
const std::map<std::string, const cricket::ContentGroup*>&
bundle_groups_by_mid) {
if (session_error() != SessionError::kNone) {
LOG_AND_RETURN_ERROR(RTCErrorType::INTERNAL_ERROR, GetSessionErrorMsg());
}
// An assumption is that a check for session error is done at a higher level.
RTC_DCHECK_EQ(SessionError::kNone, session_error());
if (!sdesc || !sdesc->description()) {
LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, kInvalidSdp);
@ -3099,8 +3216,8 @@ RTCError SdpOfferAnswerHandler::ValidateSessionDescription(
// Verify m-lines in Answer when compared against Offer.
if (type == SdpType::kPrAnswer || type == SdpType::kAnswer) {
// With an answer we want to compare the new answer session description with
// the offer's session description from the current negotiation.
// With an answer we want to compare the new answer session description
// with the offer's session description from the current negotiation.
const cricket::SessionDescription* offer_desc =
(source == cricket::CS_LOCAL) ? remote_description()->description()
: local_description()->description();
@ -3113,11 +3230,12 @@ RTCError SdpOfferAnswerHandler::ValidateSessionDescription(
} else {
// The re-offers should respect the order of m= sections in current
// description. See RFC3264 Section 8 paragraph 4 for more details.
// With a re-offer, either the current local or current remote descriptions
// could be the most up to date, so we would like to check against both of
// them if they exist. It could be the case that one of them has a 0 port
// for a media section, but the other does not. This is important to check
// against in the case that we are recycling an m= section.
// With a re-offer, either the current local or current remote
// descriptions could be the most up to date, so we would like to check
// against both of them if they exist. It could be the case that one of
// them has a 0 port for a media section, but the other does not. This is
// important to check against in the case that we are recycling an m=
// section.
const cricket::SessionDescription* current_desc = nullptr;
const cricket::SessionDescription* secondary_current_desc = nullptr;
if (local_description()) {
@ -3172,8 +3290,9 @@ RTCError SdpOfferAnswerHandler::UpdateTransceiversAndDataChannels(
if (new_session.GetType() == SdpType::kOffer) {
// If the BUNDLE policy is max-bundle, then we know for sure that all
// transports will be bundled from the start. Return an error if max-bundle
// is specified but the session description does not have a BUNDLE group.
// transports will be bundled from the start. Return an error if
// max-bundle is specified but the session description does not have a
// BUNDLE group.
if (pc_->configuration()->bundle_policy ==
PeerConnectionInterface::kBundlePolicyMaxBundle &&
bundle_groups_by_mid.empty()) {

View File

@ -182,6 +182,7 @@ class SdpOfferAnswerHandler : public SdpStateProvider,
rtc::scoped_refptr<StreamCollectionInterface> remote_streams();
private:
class RemoteDescriptionOperation;
class ImplicitCreateSessionDescriptionObserver;
friend class ImplicitCreateSessionDescriptionObserver;
@ -259,8 +260,11 @@ class SdpOfferAnswerHandler : public SdpStateProvider,
std::unique_ptr<SessionDescriptionInterface> desc,
rtc::scoped_refptr<SetLocalDescriptionObserverInterface> observer);
void DoSetRemoteDescription(
std::unique_ptr<SessionDescriptionInterface> desc,
rtc::scoped_refptr<SetRemoteDescriptionObserverInterface> observer);
std::unique_ptr<RemoteDescriptionOperation> operation);
// Called after a DoSetRemoteDescription operation completes.
void SetRemoteDescriptionPostProcess(bool was_answer)
RTC_RUN_ON(signaling_thread());
// Update the state, signaling if necessary.
void ChangeSignalingState(