| /* |
| * Copyright (C) 2022 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "host/frontend/webrtc/libcommon/connection_controller.h" |
| |
| #include <algorithm> |
| #include <vector> |
| |
| #include <android-base/logging.h> |
| |
| #include "host/frontend/webrtc/libcommon/audio_device.h" |
| #include "host/frontend/webrtc/libcommon/utils.h" |
| |
| namespace cuttlefish { |
| namespace webrtc_streaming { |
| |
| // Different classes are needed because all the interfaces inherit from |
| // classes providing the methods AddRef and Release, needed by scoped_ptr, which |
| // cause ambiguity when a single class (i.e ConnectionController) implements all |
| // of them. |
| // It's safe for these classes to hold a reference to the ConnectionController |
| // because it owns the peer connection, so it will never be destroyed before |
| // these observers. |
| class CreateSessionDescriptionObserverIntermediate |
| : public webrtc::CreateSessionDescriptionObserver { |
| public: |
| CreateSessionDescriptionObserverIntermediate(ConnectionController& controller) |
| : controller_(controller) {} |
| |
| void OnSuccess(webrtc::SessionDescriptionInterface* desc) override { |
| controller_.OnCreateSDPSuccess(desc); |
| } |
| void OnFailure(webrtc::RTCError error) override { |
| controller_.OnCreateSDPFailure(error); |
| } |
| |
| private: |
| ConnectionController& controller_; |
| }; |
| |
| class SetSessionDescriptionObserverIntermediate |
| : public webrtc::SetSessionDescriptionObserver { |
| public: |
| SetSessionDescriptionObserverIntermediate(ConnectionController& controller) |
| : controller_(controller) {} |
| |
| void OnSuccess() override { controller_.OnSetLocalDescriptionSuccess(); } |
| void OnFailure(webrtc::RTCError error) override { |
| controller_.OnSetLocalDescriptionFailure(error); |
| } |
| |
| private: |
| ConnectionController& controller_; |
| }; |
| |
| class SetRemoteDescriptionObserverIntermediate |
| : public webrtc::SetRemoteDescriptionObserverInterface { |
| public: |
| SetRemoteDescriptionObserverIntermediate(ConnectionController& controller) |
| : controller_(controller) {} |
| |
| void OnSetRemoteDescriptionComplete(webrtc::RTCError error) override { |
| controller_.OnSetRemoteDescriptionComplete(error); |
| } |
| |
| private: |
| ConnectionController& controller_; |
| }; |
| |
| ConnectionController::ConnectionController( |
| PeerSignalingHandler& sig_handler, |
| PeerConnectionBuilder& connection_builder, |
| ConnectionController::Observer& observer) |
| : sig_handler_(sig_handler), |
| connection_builder_(connection_builder), |
| observer_(observer) {} |
| |
| void ConnectionController::CreateOffer() { |
| // No memory leak here because this is a ref counted object and the |
| // peer connection immediately wraps it with a scoped_refptr |
| peer_connection_->CreateOffer(ThisAsCreateSDPObserver(), {} /*options*/); |
| } |
| |
| Result<void> ConnectionController::RequestOffer( |
| const std::vector<webrtc::PeerConnectionInterface::IceServer>& |
| ice_servers) { |
| observer_.OnConnectionStateChange( |
| webrtc::PeerConnectionInterface::PeerConnectionState::kNew); |
| Json::Value msg; |
| msg["type"] = "request-offer"; |
| if (!ice_servers.empty()) { |
| // Only include the ice servers in the message if non empty |
| msg["ice_servers"] = GenerateIceServersMessage(ice_servers); |
| } |
| CF_EXPECT(sig_handler_.SendMessage(msg), |
| "Failed to send the request-offer message to the device"); |
| return {}; |
| } |
| |
| void ConnectionController::FailConnection(const std::string& message) { |
| Json::Value reply; |
| reply["type"] = "error"; |
| reply["error"] = message; |
| sig_handler_.SendMessage(reply); |
| observer_.OnConnectionStateChange(CF_ERR(message)); |
| } |
| |
| void ConnectionController::AddPendingIceCandidates() { |
| // Add any ice candidates that arrived before the remote description |
| for (auto& candidate : pending_ice_candidates_) { |
| peer_connection_->AddIceCandidate( |
| std::move(candidate), [this](webrtc::RTCError error) { |
| if (!error.ok()) { |
| FailConnection(ToString(error.type()) + std::string(": ") + |
| error.message()); |
| } |
| }); |
| } |
| pending_ice_candidates_.clear(); |
| } |
| |
| Result<void> ConnectionController::OnOfferRequestMsg( |
| const std::vector<webrtc::PeerConnectionInterface::IceServer>& |
| ice_servers) { |
| peer_connection_ = CF_EXPECT(connection_builder_.Build(*this, ice_servers), |
| "Failed to create peer connection"); |
| CreateOffer(); |
| return {}; |
| } |
| |
| Result<void> ConnectionController::OnOfferMsg( |
| std::unique_ptr<webrtc::SessionDescriptionInterface> offer) { |
| peer_connection_->SetRemoteDescription(std::move(offer), |
| ThisAsSetRemoteSDPObserver()); |
| return {}; |
| } |
| |
| Result<void> ConnectionController::OnAnswerMsg( |
| std::unique_ptr<webrtc::SessionDescriptionInterface> answer) { |
| peer_connection_->SetRemoteDescription(std::move(answer), |
| ThisAsSetRemoteSDPObserver()); |
| return {}; |
| } |
| |
| Result<void> ConnectionController::OnIceCandidateMsg( |
| std::unique_ptr<webrtc::IceCandidateInterface> candidate) { |
| if (peer_connection_->remote_description()) { |
| peer_connection_->AddIceCandidate( |
| std::move(candidate), [this](webrtc::RTCError error) { |
| if (!error.ok()) { |
| FailConnection(ToString(error.type()) + std::string(": ") + |
| error.message()); |
| } |
| }); |
| } else { |
| // Store the ice candidate to be added later if it arrives before the |
| // remote description. This could happen if the client uses polling |
| // instead of websockets because the candidates are generated immediately |
| // after the remote (offer) description is set and the events and the ajax |
| // calls are asynchronous. |
| pending_ice_candidates_.push_back(std::move(candidate)); |
| } |
| return {}; |
| } |
| |
| Result<void> ConnectionController::OnErrorMsg(const std::string& msg) { |
| LOG(ERROR) << "Received error message from peer: " << msg; |
| return {}; |
| } |
| |
| void ConnectionController::OnCreateSDPSuccess( |
| webrtc::SessionDescriptionInterface* desc) { |
| std::string offer_str; |
| desc->ToString(&offer_str); |
| std::string sdp_type = desc->type(); |
| peer_connection_->SetLocalDescription(ThisAsSetSDPObserver(), desc); |
| // The peer connection takes ownership of the description so it should not be |
| // used after this |
| desc = nullptr; |
| |
| Json::Value reply; |
| reply["type"] = sdp_type; |
| reply["sdp"] = offer_str; |
| |
| sig_handler_.SendMessage(reply); |
| } |
| |
| void ConnectionController::OnCreateSDPFailure(const webrtc::RTCError& error) { |
| FailConnection(ToString(error.type()) + std::string(": ") + error.message()); |
| } |
| |
| void ConnectionController::OnSetLocalDescriptionSuccess() { |
| // local description set, nothing else to do |
| } |
| |
| void ConnectionController::OnSetLocalDescriptionFailure( |
| const webrtc::RTCError& error) { |
| LOG(ERROR) << "Error setting local description: Either there is a bug in " |
| "libwebrtc or the local description was (incorrectly) modified " |
| "after creating it"; |
| FailConnection(ToString(error.type()) + std::string(": ") + error.message()); |
| } |
| |
| void ConnectionController::OnSetRemoteDescriptionComplete( |
| const webrtc::RTCError& error) { |
| if (!error.ok()) { |
| // The remote description was rejected, can't connect to device. |
| FailConnection(ToString(error.type()) + std::string(": ") + error.message()); |
| return; |
| } |
| AddPendingIceCandidates(); |
| auto remote_desc = peer_connection_->remote_description(); |
| CHECK(remote_desc) << "The remote description was just added successfully in " |
| "this thread, so it can't be nullptr"; |
| if (remote_desc->GetType() != webrtc::SdpType::kOffer) { |
| // Only create and send answer when the remote description is an offer. |
| return; |
| } |
| peer_connection_->CreateAnswer(ThisAsCreateSDPObserver(), {} /*options*/); |
| } |
| |
| // No memory leaks with these because the peer_connection immediately wraps |
| // these pointers with scoped_refptr. |
| webrtc::CreateSessionDescriptionObserver* |
| ConnectionController::ThisAsCreateSDPObserver() { |
| return new rtc::RefCountedObject< |
| CreateSessionDescriptionObserverIntermediate>(*this); |
| } |
| webrtc::SetSessionDescriptionObserver* |
| ConnectionController::ThisAsSetSDPObserver() { |
| return new rtc::RefCountedObject<SetSessionDescriptionObserverIntermediate>( |
| *this); |
| } |
| rtc::scoped_refptr<webrtc::SetRemoteDescriptionObserverInterface> |
| ConnectionController::ThisAsSetRemoteSDPObserver() { |
| return rtc::scoped_refptr<webrtc::SetRemoteDescriptionObserverInterface>( |
| new rtc::RefCountedObject<SetRemoteDescriptionObserverIntermediate>( |
| *this)); |
| } |
| |
| void ConnectionController::HandleSignalingMessage(const Json::Value& msg) { |
| auto result = HandleSignalingMessageInner(msg); |
| if (!result.ok()) { |
| LOG(ERROR) << result.error().FormatForEnv(); |
| FailConnection(result.error().Message()); |
| } |
| } |
| |
| Result<void> ConnectionController::HandleSignalingMessageInner( |
| const Json::Value& message) { |
| CF_EXPECT(ValidateJsonObject(message, "", |
| {{"type", Json::ValueType::stringValue}})); |
| auto type = message["type"].asString(); |
| |
| if (type == "request-offer") { |
| auto ice_servers = CF_EXPECT(ParseIceServersMessage(message), |
| "Error parsing ice-servers field"); |
| return OnOfferRequestMsg(ice_servers); |
| } else if (type == "offer") { |
| auto remote_desc = CF_EXPECT( |
| ParseSessionDescription(type, message, webrtc::SdpType::kOffer)); |
| return OnOfferMsg(std::move(remote_desc)); |
| } else if (type == "answer") { |
| auto remote_desc = CF_EXPECT( |
| ParseSessionDescription(type, message, webrtc::SdpType::kAnswer)); |
| return OnAnswerMsg(std::move(remote_desc)); |
| } else if (type == "ice-candidate") { |
| auto candidate = CF_EXPECT(ParseIceCandidate(type, message)); |
| return OnIceCandidateMsg(std::move(candidate)); |
| } else if (type == "error") { |
| return OnErrorMsg(CF_EXPECT(ParseError(type, message))); |
| } else { |
| return CF_ERR("Unknown client message type: " + type); |
| } |
| } |
| |
| // Triggered when the SignalingState changed. |
| void ConnectionController::OnSignalingChange( |
| webrtc::PeerConnectionInterface::SignalingState new_state) { |
| LOG(VERBOSE) << "Signaling state changed: " << new_state; |
| } |
| |
| // Triggered when media is received on a new stream from remote peer. |
| void ConnectionController::OnAddStream( |
| rtc::scoped_refptr<webrtc::MediaStreamInterface> stream) { |
| LOG(VERBOSE) << "Stream added: " << stream->id(); |
| } |
| |
| // Triggered when a remote peer closes a stream. |
| void ConnectionController::OnRemoveStream( |
| rtc::scoped_refptr<webrtc::MediaStreamInterface> stream) { |
| LOG(VERBOSE) << "Stream removed: " << stream->id(); |
| } |
| |
| // Triggered when a remote peer opens a data channel. |
| void ConnectionController::OnDataChannel( |
| rtc::scoped_refptr<webrtc::DataChannelInterface> data_channel) { |
| observer_.OnDataChannel(data_channel); |
| } |
| |
| // Triggered when renegotiation is needed. For example, an ICE restart |
| // has begun. |
| void ConnectionController::OnRenegotiationNeeded() { |
| if (!peer_connection_) { |
| return; |
| } |
| CreateOffer(); |
| } |
| |
| // Called any time the standards-compliant IceConnectionState changes. |
| void ConnectionController::OnStandardizedIceConnectionChange( |
| webrtc::PeerConnectionInterface::IceConnectionState new_state) { |
| switch (new_state) { |
| case webrtc::PeerConnectionInterface::kIceConnectionNew: |
| LOG(DEBUG) << "ICE connection state: New"; |
| break; |
| case webrtc::PeerConnectionInterface::kIceConnectionChecking: |
| LOG(DEBUG) << "ICE connection state: Checking"; |
| break; |
| case webrtc::PeerConnectionInterface::kIceConnectionConnected: |
| LOG(DEBUG) << "ICE connection state: Connected"; |
| break; |
| case webrtc::PeerConnectionInterface::kIceConnectionCompleted: |
| LOG(DEBUG) << "ICE connection state: Completed"; |
| break; |
| case webrtc::PeerConnectionInterface::kIceConnectionFailed: |
| LOG(DEBUG) << "ICE connection state: Failed"; |
| break; |
| case webrtc::PeerConnectionInterface::kIceConnectionDisconnected: |
| LOG(DEBUG) << "ICE connection state: Disconnected"; |
| break; |
| case webrtc::PeerConnectionInterface::kIceConnectionClosed: |
| LOG(DEBUG) << "ICE connection state: Closed"; |
| break; |
| case webrtc::PeerConnectionInterface::kIceConnectionMax: |
| LOG(DEBUG) << "ICE connection state: Max"; |
| break; |
| default: |
| LOG(DEBUG) << "ICE connection state: " << new_state; |
| } |
| } |
| |
| // Called any time the PeerConnectionState changes. |
| void ConnectionController::OnConnectionChange( |
| webrtc::PeerConnectionInterface::PeerConnectionState new_state) { |
| observer_.OnConnectionStateChange(new_state); |
| } |
| |
| // Called any time the IceGatheringState changes. |
| void ConnectionController::OnIceGatheringChange( |
| webrtc::PeerConnectionInterface::IceGatheringState new_state) { |
| std::string state_str; |
| switch (new_state) { |
| case webrtc::PeerConnectionInterface::IceGatheringState::kIceGatheringNew: |
| state_str = "NEW"; |
| break; |
| case webrtc::PeerConnectionInterface::IceGatheringState:: |
| kIceGatheringGathering: |
| state_str = "GATHERING"; |
| break; |
| case webrtc::PeerConnectionInterface::IceGatheringState:: |
| kIceGatheringComplete: |
| state_str = "COMPLETE"; |
| break; |
| default: |
| state_str = "UNKNOWN"; |
| } |
| LOG(VERBOSE) << "ICE Gathering state set to: " << state_str; |
| } |
| |
| // A new ICE candidate has been gathered. |
| void ConnectionController::OnIceCandidate( |
| const webrtc::IceCandidateInterface* candidate) { |
| std::string candidate_sdp; |
| candidate->ToString(&candidate_sdp); |
| auto sdp_mid = candidate->sdp_mid(); |
| auto line_index = candidate->sdp_mline_index(); |
| |
| Json::Value reply; |
| reply["type"] = "ice-candidate"; |
| reply["mid"] = sdp_mid; |
| reply["mLineIndex"] = static_cast<Json::UInt64>(line_index); |
| reply["candidate"] = candidate_sdp; |
| |
| sig_handler_.SendMessage(reply); |
| } |
| |
| // Gathering of an ICE candidate failed. |
| // See https://w3c.github.io/webrtc-pc/#event-icecandidateerror |
| void ConnectionController::OnIceCandidateError(const std::string& address, |
| int port, const std::string& url, |
| int error_code, |
| const std::string& error_text) { |
| LOG(VERBOSE) << "Gathering of an ICE candidate (address: " << address |
| << ", port: " << port << ", url: " << url |
| << ") failed: " << error_text; |
| } |
| |
| // Ice candidates have been removed. |
| void ConnectionController::OnIceCandidatesRemoved( |
| const std::vector<cricket::Candidate>&) { |
| // ignore |
| } |
| |
| // This is called when signaling indicates a transceiver will be receiving |
| // media from the remote endpoint. This is fired during a call to |
| // SetRemoteDescription. The receiving track can be accessed by: |
| // ConnectionController::|transceiver->receiver()->track()| and its |
| // associated streams by |transceiver->receiver()->streams()|. Note: This will |
| // only be called if Unified Plan semantics are specified. This behavior is |
| // specified in section 2.2.8.2.5 of the "Set the RTCSessionDescription" |
| // algorithm: https://w3c.github.io/webrtc-pc/#set-description |
| void ConnectionController::OnTrack( |
| rtc::scoped_refptr<webrtc::RtpTransceiverInterface> transceiver) { |
| observer_.OnTrack(transceiver); |
| } |
| |
| // Called when signaling indicates that media will no longer be received on a |
| // track. |
| // With Plan B semantics, the given receiver will have been removed from the |
| // PeerConnection and the track muted. |
| // With Unified Plan semantics, the receiver will remain but the transceiver |
| // will have changed direction to either sendonly or inactive. |
| // https://w3c.github.io/webrtc-pc/#process-remote-track-removal |
| void ConnectionController::OnRemoveTrack( |
| rtc::scoped_refptr<webrtc::RtpReceiverInterface> receiver) { |
| observer_.OnRemoveTrack(receiver); |
| } |
| |
| } // namespace webrtc_streaming |
| } // namespace cuttlefish |
| |