| // Copyright 2013 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "third_party/blink/renderer/modules/peerconnection/peer_connection_tracker.h" |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include <memory> |
| #include <optional> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/containers/contains.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/types/pass_key.h" |
| #include "base/values.h" |
| #include "build/build_config.h" |
| #include "build/buildflag.h" |
| #include "build/chromecast_buildflags.h" |
| #include "third_party/blink/public/mojom/peerconnection/peer_connection_tracker.mojom-blink.h" |
| #include "third_party/blink/public/platform/browser_interface_broker_proxy.h" |
| #include "third_party/blink/public/platform/interface_registry.h" |
| #include "third_party/blink/public/platform/modules/mediastream/web_media_stream.h" |
| #include "third_party/blink/public/platform/platform.h" |
| #include "third_party/blink/public/web/web_document.h" |
| #include "third_party/blink/public/web/web_local_frame.h" |
| #include "third_party/blink/renderer/core/execution_context/execution_context.h" |
| #include "third_party/blink/renderer/core/frame/local_dom_window.h" |
| #include "third_party/blink/renderer/core/frame/local_frame.h" |
| #include "third_party/blink/renderer/core/frame/web_local_frame_impl.h" |
| #include "third_party/blink/renderer/core/loader/document_loader.h" |
| #include "third_party/blink/renderer/modules/mediastream/media_constraints.h" |
| #include "third_party/blink/renderer/modules/mediastream/user_media_request.h" |
| #include "third_party/blink/renderer/modules/peerconnection/rtc_peer_connection_handler.h" |
| #include "third_party/blink/renderer/platform/mediastream/media_stream_component.h" |
| #include "third_party/blink/renderer/platform/mojo/mojo_binding_context.h" |
| #include "third_party/blink/renderer/platform/peerconnection/rtc_answer_options_platform.h" |
| #include "third_party/blink/renderer/platform/peerconnection/rtc_ice_candidate_platform.h" |
| #include "third_party/blink/renderer/platform/peerconnection/rtc_offer_options_platform.h" |
| #include "third_party/blink/renderer/platform/peerconnection/rtc_peer_connection_handler_client.h" |
| #include "third_party/blink/renderer/platform/peerconnection/webrtc_util.h" |
| #include "third_party/blink/renderer/platform/scheduler/public/post_cross_thread_task.h" |
| #include "third_party/blink/renderer/platform/scheduler/public/thread.h" |
| #include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h" |
| #include "third_party/blink/renderer/platform/wtf/text/string_builder.h" |
| #include "third_party/webrtc/api/stats/rtcstats_objects.h" |
| |
| using webrtc::StatsReport; |
| using webrtc::StatsReports; |
| |
| namespace blink { |
| |
| class InternalStandardStatsObserver; |
| |
| template <> |
| struct CrossThreadCopier<scoped_refptr<InternalStandardStatsObserver>> |
| : public CrossThreadCopierPassThrough< |
| scoped_refptr<InternalStandardStatsObserver>> { |
| STATIC_ONLY(CrossThreadCopier); |
| }; |
| |
| template <typename T> |
| struct CrossThreadCopier<webrtc::scoped_refptr<T>> { |
| STATIC_ONLY(CrossThreadCopier); |
| using Type = webrtc::scoped_refptr<T>; |
| static Type Copy(Type pointer) { return pointer; } |
| }; |
| |
| template <> |
| struct CrossThreadCopier<base::Value::List> |
| : public CrossThreadCopierByValuePassThrough<base::Value::List> { |
| STATIC_ONLY(CrossThreadCopier); |
| }; |
| |
| // TODO(hta): This module should be redesigned to reduce string copies. |
| |
| namespace { |
| |
| String SerializeServers( |
| const std::vector<webrtc::PeerConnectionInterface::IceServer>& servers) { |
| StringBuilder result; |
| result.Append("["); |
| |
| bool following = false; |
| for (const auto& server : servers) { |
| for (const auto& url : server.urls) { |
| if (following) |
| result.Append(", "); |
| else |
| following = true; |
| |
| result.Append(String::FromUTF8(url)); |
| } |
| } |
| result.Append("]"); |
| return result.ToString(); |
| } |
| |
| String SerializeGetUserMediaMediaConstraints( |
| const MediaConstraints& constraints) { |
| return String(constraints.ToString()); |
| } |
| |
| String SerializeOfferOptions(blink::RTCOfferOptionsPlatform* options) { |
| if (!options) |
| return "null"; |
| |
| StringBuilder result; |
| result.Append("offerToReceiveVideo: "); |
| result.AppendNumber(options->OfferToReceiveVideo()); |
| result.Append(", offerToReceiveAudio: "); |
| result.AppendNumber(options->OfferToReceiveAudio()); |
| result.Append(", voiceActivityDetection: "); |
| result.Append(String::Boolean(options->VoiceActivityDetection())); |
| result.Append(", iceRestart: "); |
| result.Append(String::Boolean(options->IceRestart())); |
| return result.ToString(); |
| } |
| |
| String SerializeAnswerOptions(blink::RTCAnswerOptionsPlatform* options) { |
| if (!options) |
| return "null"; |
| |
| StringBuilder result; |
| result.Append(", voiceActivityDetection: "); |
| result.Append(String::Boolean(options->VoiceActivityDetection())); |
| return result.ToString(); |
| } |
| |
| String SerializeMediaStreamIds(const Vector<String>& stream_ids) { |
| if (!stream_ids.size()) |
| return "[]"; |
| StringBuilder result; |
| result.Append("["); |
| for (const auto& stream_id : stream_ids) { |
| if (result.length() > 2u) |
| result.Append(","); |
| result.Append("'"); |
| result.Append(stream_id); |
| result.Append("'"); |
| } |
| result.Append("]"); |
| return result.ToString(); |
| } |
| |
| String SerializeDirection(webrtc::RtpTransceiverDirection direction) { |
| switch (direction) { |
| case webrtc::RtpTransceiverDirection::kSendRecv: |
| return "'sendrecv'"; |
| case webrtc::RtpTransceiverDirection::kSendOnly: |
| return "'sendonly'"; |
| case webrtc::RtpTransceiverDirection::kRecvOnly: |
| return "'recvonly'"; |
| case webrtc::RtpTransceiverDirection::kInactive: |
| return "'inactive'"; |
| case webrtc::RtpTransceiverDirection::kStopped: |
| return "'stopped'"; |
| default: |
| NOTREACHED(); |
| } |
| } |
| |
| String SerializeOptionalDirection( |
| const std::optional<webrtc::RtpTransceiverDirection>& direction) { |
| return direction ? SerializeDirection(*direction) : "null"; |
| } |
| |
| String SerializeTransceiverKind(const String& indent, |
| const RTCRtpTransceiverPlatform& transceiver) { |
| DCHECK(transceiver.Receiver()); |
| DCHECK(transceiver.Receiver()->Track()); |
| |
| auto kind = transceiver.Receiver()->Track()->GetSourceType(); |
| StringBuilder result; |
| result.Append(indent); |
| result.Append("kind:"); |
| if (kind == MediaStreamSource::StreamType::kTypeAudio) { |
| result.Append("'audio'"); |
| } else if (kind == MediaStreamSource::StreamType::kTypeVideo) { |
| result.Append("'video'"); |
| } else { |
| NOTREACHED(); |
| } |
| result.Append(",\n"); |
| return result.ToString(); |
| } |
| |
| String SerializeEncodingParameters( |
| const String& indent, |
| const std::vector<webrtc::RtpEncodingParameters>& encodings) { |
| StringBuilder result; |
| if (encodings.empty()) { |
| return result.ToString(); |
| } |
| result.Append(indent); |
| result.Append("encodings: [\n"); |
| for (const auto& encoding : encodings) { |
| result.Append(indent); |
| result.Append(" {"); |
| result.Append("active: "); |
| result.Append(String::Boolean(encoding.active)); |
| result.Append(", "); |
| if (encoding.max_bitrate_bps) { |
| result.Append("maxBitrate: "); |
| result.AppendNumber(*encoding.max_bitrate_bps); |
| result.Append(", "); |
| } |
| if (encoding.scale_resolution_down_by) { |
| result.Append("scaleResolutionDownBy: "); |
| result.AppendNumber(*encoding.scale_resolution_down_by); |
| result.Append(", "); |
| } |
| if (!encoding.rid.empty()) { |
| result.Append("rid: "); |
| result.Append(String(encoding.rid)); |
| result.Append(", "); |
| } |
| if (encoding.max_framerate) { |
| result.Append("maxFramerate: "); |
| result.AppendNumber(*encoding.max_framerate); |
| result.Append(", "); |
| } |
| if (encoding.adaptive_ptime) { |
| result.Append("adaptivePtime: true, "); |
| } |
| if (encoding.scalability_mode) { |
| result.Append("scalabilityMode: "); |
| result.Append(String(*encoding.scalability_mode)); |
| } |
| result.Append("},\n"); |
| } |
| result.Append(indent); |
| result.Append(" ],\n"); |
| result.Append(indent); |
| return result.ToString(); |
| } |
| |
| String SerializeSender(const String& indent, |
| const blink::RTCRtpSenderPlatform& sender) { |
| StringBuilder result; |
| result.Append(indent); |
| result.Append("sender:{\n"); |
| // track:'id', |
| result.Append(indent); |
| result.Append(" track:"); |
| if (!sender.Track()) { |
| result.Append("null"); |
| } else { |
| result.Append("'"); |
| result.Append(sender.Track()->Id()); |
| result.Append("'"); |
| } |
| result.Append(",\n"); |
| // streams:['id,'id'], |
| result.Append(indent); |
| result.Append(" streams:"); |
| result.Append(SerializeMediaStreamIds(sender.StreamIds())); |
| result.Append(",\n"); |
| result.Append(indent); |
| result.Append( |
| SerializeEncodingParameters(indent, sender.GetParameters()->encodings)); |
| result.Append("},\n"); |
| |
| return result.ToString(); |
| } |
| |
| String SerializeReceiver(const String& indent, |
| const RTCRtpReceiverPlatform& receiver) { |
| StringBuilder result; |
| result.Append(indent); |
| result.Append("receiver:{\n"); |
| // track:'id', |
| DCHECK(receiver.Track()); |
| result.Append(indent); |
| result.Append(" track:'"); |
| result.Append(receiver.Track()->Id()); |
| result.Append("',\n"); |
| // streams:['id,'id'], |
| result.Append(indent); |
| result.Append(" streams:"); |
| result.Append(SerializeMediaStreamIds(receiver.StreamIds())); |
| result.Append(",\n"); |
| result.Append(indent); |
| result.Append("},\n"); |
| return result.ToString(); |
| } |
| |
| String SerializeTransceiver(const RTCRtpTransceiverPlatform& transceiver) { |
| StringBuilder result; |
| result.Append("{\n"); |
| // mid:'foo', |
| if (transceiver.Mid().IsNull()) { |
| result.Append(" mid:null,\n"); |
| } else { |
| result.Append(" mid:'"); |
| result.Append(String(transceiver.Mid())); |
| result.Append("',\n"); |
| } |
| // kind:audio|video |
| result.Append(SerializeTransceiverKind(" ", transceiver)); |
| // sender:{...}, |
| result.Append(SerializeSender(" ", *transceiver.Sender())); |
| // receiver:{...}, |
| result.Append(SerializeReceiver(" ", *transceiver.Receiver())); |
| // direction:'sendrecv', |
| result.Append(" direction:"); |
| result.Append(SerializeDirection(transceiver.Direction())); |
| result.Append(",\n"); |
| // currentDirection:null, |
| result.Append(" currentDirection:"); |
| result.Append(SerializeOptionalDirection(transceiver.CurrentDirection())); |
| result.Append(",\n"); |
| result.Append("}"); |
| return result.ToString(); |
| } |
| |
| String SerializeIceTransportType( |
| webrtc::PeerConnectionInterface::IceTransportsType type) { |
| String transport_type(""); |
| switch (type) { |
| case webrtc::PeerConnectionInterface::kNone: |
| transport_type = "none"; |
| break; |
| case webrtc::PeerConnectionInterface::kRelay: |
| transport_type = "relay"; |
| break; |
| case webrtc::PeerConnectionInterface::kAll: |
| transport_type = "all"; |
| break; |
| case webrtc::PeerConnectionInterface::kNoHost: |
| transport_type = "noHost"; |
| break; |
| default: |
| NOTREACHED(); |
| } |
| return transport_type; |
| } |
| |
| String SerializeBundlePolicy( |
| webrtc::PeerConnectionInterface::BundlePolicy policy) { |
| String policy_str(""); |
| switch (policy) { |
| case webrtc::PeerConnectionInterface::kBundlePolicyBalanced: |
| policy_str = "balanced"; |
| break; |
| case webrtc::PeerConnectionInterface::kBundlePolicyMaxBundle: |
| policy_str = "max-bundle"; |
| break; |
| case webrtc::PeerConnectionInterface::kBundlePolicyMaxCompat: |
| policy_str = "max-compat"; |
| break; |
| default: |
| NOTREACHED(); |
| } |
| return policy_str; |
| } |
| |
| String SerializeRtcpMuxPolicy( |
| webrtc::PeerConnectionInterface::RtcpMuxPolicy policy) { |
| String policy_str(""); |
| switch (policy) { |
| case webrtc::PeerConnectionInterface::kRtcpMuxPolicyNegotiate: |
| policy_str = "negotiate"; |
| break; |
| case webrtc::PeerConnectionInterface::kRtcpMuxPolicyRequire: |
| policy_str = "require"; |
| break; |
| default: |
| NOTREACHED(); |
| } |
| return policy_str; |
| } |
| |
| // Serializes things that are of interest from the RTCConfiguration. |
| String SerializeConfiguration( |
| const webrtc::PeerConnectionInterface::RTCConfiguration& config, |
| bool usesInsertableStreams) { |
| StringBuilder result; |
| // TODO(hbos): Add serialization of certificate. |
| result.Append("{ iceServers: "); |
| result.Append(SerializeServers(config.servers)); |
| result.Append(", iceTransportPolicy: "); |
| result.Append(SerializeIceTransportType(config.type)); |
| result.Append(", bundlePolicy: "); |
| result.Append(SerializeBundlePolicy(config.bundle_policy)); |
| result.Append(", rtcpMuxPolicy: "); |
| result.Append(SerializeRtcpMuxPolicy(config.rtcp_mux_policy)); |
| result.Append(", iceCandidatePoolSize: "); |
| result.AppendNumber(config.ice_candidate_pool_size); |
| if (usesInsertableStreams) { |
| result.Append(", encodedInsertableStreams: true"); |
| } |
| result.Append(" }"); |
| return result.ToString(); |
| } |
| |
| const char* GetTransceiverUpdatedReasonString( |
| PeerConnectionTracker::TransceiverUpdatedReason reason) { |
| switch (reason) { |
| case PeerConnectionTracker::TransceiverUpdatedReason::kAddTransceiver: |
| return "addTransceiver"; |
| case PeerConnectionTracker::TransceiverUpdatedReason::kAddTrack: |
| return "addTrack"; |
| case PeerConnectionTracker::TransceiverUpdatedReason::kRemoveTrack: |
| return "removeTrack"; |
| case PeerConnectionTracker::TransceiverUpdatedReason::kSetLocalDescription: |
| return "setLocalDescription"; |
| case PeerConnectionTracker::TransceiverUpdatedReason::kSetRemoteDescription: |
| return "setRemoteDescription"; |
| } |
| NOTREACHED(); |
| } |
| |
| int GetNextProcessLocalID() { |
| static int next_local_id = 1; |
| return next_local_id++; |
| } |
| |
| } // namespace |
| |
| // chrome://webrtc-internals displays stats and stats graphs. The call path |
| // involves thread and process hops (IPC). This is the stats observer that is |
| // used when webrtc-internals wants standard stats. It starts in |
| // webrtc_internals.js performing requestStandardStats and the result gets |
| // asynchronously delivered to webrtc_internals.js at addStandardStats. |
| class InternalStandardStatsObserver : public webrtc::RTCStatsCollectorCallback { |
| public: |
| InternalStandardStatsObserver( |
| const base::WeakPtr<RTCPeerConnectionHandler> pc_handler, |
| int lid, |
| scoped_refptr<base::SingleThreadTaskRunner> main_thread, |
| Vector<std::unique_ptr<blink::RTCRtpSenderPlatform>> senders, |
| CrossThreadOnceFunction<void(int, base::Value::List)> completion_callback) |
| : pc_handler_(pc_handler), |
| lid_(lid), |
| main_thread_(std::move(main_thread)), |
| senders_(std::move(senders)), |
| completion_callback_(std::move(completion_callback)) {} |
| |
| void OnStatsDelivered( |
| const webrtc::scoped_refptr<const webrtc::RTCStatsReport>& report) |
| override { |
| // We're on the signaling thread. |
| DCHECK(!main_thread_->BelongsToCurrentThread()); |
| PostCrossThreadTask( |
| *main_thread_.get(), FROM_HERE, |
| CrossThreadBindOnce( |
| &InternalStandardStatsObserver::OnStatsDeliveredOnMainThread, |
| scoped_refptr<InternalStandardStatsObserver>(this), report)); |
| } |
| |
| protected: |
| ~InternalStandardStatsObserver() override {} |
| |
| private: |
| void OnStatsDeliveredOnMainThread( |
| webrtc::scoped_refptr<const webrtc::RTCStatsReport> report) { |
| std::move(completion_callback_).Run(lid_, ReportToList(report)); |
| } |
| |
| base::Value::List ReportToList( |
| const webrtc::scoped_refptr<const webrtc::RTCStatsReport>& report) { |
| std::map<std::string, MediaStreamTrackPlatform*> tracks_by_id; |
| for (const auto& sender : senders_) { |
| MediaStreamComponent* track_component = sender->Track(); |
| if (!track_component) { |
| continue; |
| } |
| tracks_by_id.insert(std::make_pair(track_component->Id().Utf8(), |
| track_component->GetPlatformTrack())); |
| } |
| |
| base::Value::List result_list; |
| |
| if (!pc_handler_) { |
| return result_list; |
| } |
| auto* local_frame = To<WebLocalFrameImpl>(*pc_handler_->frame()).GetFrame(); |
| DocumentLoadTiming& time_converter = |
| local_frame->Loader().GetDocumentLoader()->GetTiming(); |
| // Used for string comparisons with const char* below. |
| const std::string kTypeMediaSource = "media-source"; |
| for (const auto& stats : *report) { |
| // The format of "stats_subdictionary" is: |
| // {timestamp:<milliseconds>, values: [<key-value pairs>]} |
| // The timestamp unit is milliseconds but we want decimal |
| // precision so we convert ourselves. |
| base::Value::Dict stats_subdictionary; |
| base::TimeDelta monotonic_time = |
| time_converter.MonotonicTimeToPseudoWallTime( |
| ConvertToBaseTimeTicks(stats.timestamp())); |
| stats_subdictionary.Set( |
| "timestamp", |
| monotonic_time.InMicrosecondsF() / |
| static_cast<double>(base::Time::kMicrosecondsPerMillisecond)); |
| // Values are reported as |
| // "values": ["attribute1", value, "attribute2", value...] |
| base::Value::List name_value_pairs; |
| for (const auto& attribute : stats.Attributes()) { |
| if (!attribute.has_value()) { |
| continue; |
| } |
| name_value_pairs.Append(attribute.name()); |
| name_value_pairs.Append(AttributeToValue(attribute)); |
| } |
| // Modify "media-source" to also contain the result of the |
| // MediaStreamTrack Statistics API, if applicable. |
| if (stats.type() == kTypeMediaSource) { |
| const webrtc::RTCMediaSourceStats& media_source = |
| static_cast<const webrtc::RTCMediaSourceStats&>(stats); |
| if (media_source.kind.has_value() && *media_source.kind == "video" && |
| media_source.track_identifier.has_value()) { |
| auto it = tracks_by_id.find(*media_source.track_identifier); |
| if (it != tracks_by_id.end()) { |
| MediaStreamTrackPlatform::VideoFrameStats video_frame_stats = |
| it->second->GetVideoFrameStats(); |
| name_value_pairs.Append("track.deliveredFrames"); |
| name_value_pairs.Append(base::Value( |
| static_cast<int>(video_frame_stats.deliverable_frames))); |
| name_value_pairs.Append("track.discardedFrames"); |
| name_value_pairs.Append(base::Value( |
| static_cast<int>(video_frame_stats.discarded_frames))); |
| name_value_pairs.Append("track.totalFrames"); |
| name_value_pairs.Append(base::Value( |
| static_cast<int>(video_frame_stats.deliverable_frames + |
| video_frame_stats.discarded_frames + |
| video_frame_stats.dropped_frames))); |
| } |
| } |
| } |
| stats_subdictionary.Set("values", std::move(name_value_pairs)); |
| |
| // The format of "stats_dictionary" is: |
| // {id:<string>, stats:<stats_subdictionary>, type:<string>} |
| base::Value::Dict stats_dictionary; |
| stats_dictionary.Set("stats", std::move(stats_subdictionary)); |
| stats_dictionary.Set("id", stats.id()); |
| stats_dictionary.Set("type", stats.type()); |
| result_list.Append(std::move(stats_dictionary)); |
| } |
| return result_list; |
| } |
| |
| base::Value AttributeToValue(const webrtc::Attribute& attribute) { |
| // Types supported by `base::Value` are passed as the appropriate type. |
| if (attribute.holds_alternative<bool>()) { |
| return base::Value(attribute.get<bool>()); |
| } |
| if (attribute.holds_alternative<int32_t>()) { |
| return base::Value(attribute.get<int32_t>()); |
| } |
| if (attribute.holds_alternative<std::string>()) { |
| return base::Value(attribute.get<std::string>()); |
| } |
| if (attribute.holds_alternative<double>()) { |
| return base::Value(attribute.get<double>()); |
| } |
| // Types not supported by `base::Value` are converted to string. |
| return base::Value(attribute.ToString()); |
| } |
| |
| const base::WeakPtr<RTCPeerConnectionHandler> pc_handler_; |
| const int lid_; |
| const scoped_refptr<base::SingleThreadTaskRunner> main_thread_; |
| const Vector<std::unique_ptr<blink::RTCRtpSenderPlatform>> senders_; |
| CrossThreadOnceFunction<void(int, base::Value::List)> completion_callback_; |
| }; |
| |
| // static |
| const char PeerConnectionTracker::kSupplementName[] = "PeerConnectionTracker"; |
| |
| PeerConnectionTracker& PeerConnectionTracker::From(LocalDOMWindow& window) { |
| PeerConnectionTracker* tracker = |
| Supplement<LocalDOMWindow>::From<PeerConnectionTracker>(window); |
| if (!tracker) { |
| tracker = MakeGarbageCollected<PeerConnectionTracker>( |
| window, window.GetTaskRunner(TaskType::kNetworking), |
| base::PassKey<PeerConnectionTracker>()); |
| ProvideTo(window, tracker); |
| } |
| return *tracker; |
| } |
| |
| PeerConnectionTracker* PeerConnectionTracker::From(LocalFrame& frame) { |
| auto* window = frame.DomWindow(); |
| return window ? &From(*window) : nullptr; |
| } |
| |
| PeerConnectionTracker* PeerConnectionTracker::From(WebLocalFrame& frame) { |
| auto* local_frame = To<WebLocalFrameImpl>(frame).GetFrame(); |
| return local_frame ? From(*local_frame) : nullptr; |
| } |
| |
| void PeerConnectionTracker::BindToFrame( |
| LocalFrame* frame, |
| mojo::PendingReceiver<blink::mojom::blink::PeerConnectionManager> |
| receiver) { |
| if (!frame) |
| return; |
| |
| if (auto* tracker = From(*frame)) |
| tracker->Bind(std::move(receiver)); |
| } |
| |
| PeerConnectionTracker::PeerConnectionTracker( |
| LocalDOMWindow& window, |
| scoped_refptr<base::SingleThreadTaskRunner> main_thread_task_runner, |
| base::PassKey<PeerConnectionTracker>) |
| : Supplement<LocalDOMWindow>(window), |
| // Do not set a lifecycle notifier for `peer_connection_tracker_host_` to |
| // ensure that its mojo pipe stays alive until the execution context is |
| // destroyed. `RTCPeerConnection`, which owns a `RTCPeerConnectionHandler` |
| // which keeps `this` alive, will to close and unregister the peer |
| // connection when the execution context is destroyed. For this to happen, |
| // the mojo pipe _must_ be alive to relay. See https://crbug.com/1426377 |
| // for details. |
| peer_connection_tracker_host_(nullptr), |
| receiver_(this, &window), |
| main_thread_task_runner_(std::move(main_thread_task_runner)) { |
| window.GetBrowserInterfaceBroker().GetInterface( |
| peer_connection_tracker_host_.BindNewPipeAndPassReceiver( |
| main_thread_task_runner_)); |
| } |
| |
| // Constructor used for testing. Note that receiver_ doesn't have a context |
| // notifier in this case. |
| PeerConnectionTracker::PeerConnectionTracker( |
| mojo::PendingRemote<blink::mojom::blink::PeerConnectionTrackerHost> host, |
| scoped_refptr<base::SingleThreadTaskRunner> main_thread_task_runner) |
| : Supplement(nullptr), |
| peer_connection_tracker_host_(nullptr), |
| receiver_(this, nullptr), |
| main_thread_task_runner_(std::move(main_thread_task_runner)) { |
| peer_connection_tracker_host_.Bind(std::move(host), main_thread_task_runner_); |
| } |
| |
| PeerConnectionTracker::~PeerConnectionTracker() {} |
| |
| void PeerConnectionTracker::Bind( |
| mojo::PendingReceiver<blink::mojom::blink::PeerConnectionManager> |
| receiver) { |
| DCHECK_CALLED_ON_VALID_THREAD(main_thread_); |
| receiver_.Bind(std::move(receiver), GetSupplementable()->GetTaskRunner( |
| TaskType::kMiscPlatformAPI)); |
| } |
| |
| void PeerConnectionTracker::OnSuspend() { |
| DCHECK_CALLED_ON_VALID_THREAD(main_thread_); |
| // Closing peer connections fires events. If JavaScript triggers the creation |
| // or garbage collection of more peer connections, this would invalidate the |
| // |peer_connection_local_id_map_| iterator. Therefor we iterate on a copy. |
| PeerConnectionLocalIdMap peer_connection_map_copy = |
| peer_connection_local_id_map_; |
| for (const auto& pair : peer_connection_map_copy) { |
| RTCPeerConnectionHandler* peer_connection_handler = pair.key; |
| if (!base::Contains(peer_connection_local_id_map_, |
| peer_connection_handler)) { |
| // Skip peer connections that have been unregistered during this method |
| // call. Avoids use-after-free. |
| continue; |
| } |
| peer_connection_handler->CloseClientPeerConnection(); |
| } |
| } |
| |
| void PeerConnectionTracker::OnThermalStateChange( |
| mojom::blink::DeviceThermalState thermal_state) { |
| DCHECK_CALLED_ON_VALID_THREAD(main_thread_); |
| current_thermal_state_ = thermal_state; |
| for (auto& entry : peer_connection_local_id_map_) { |
| entry.key->OnThermalStateChange(current_thermal_state_); |
| } |
| } |
| |
| void PeerConnectionTracker::StartEventLog(int peer_connection_local_id, |
| int output_period_ms) { |
| DCHECK_CALLED_ON_VALID_THREAD(main_thread_); |
| for (auto& it : peer_connection_local_id_map_) { |
| if (it.value == peer_connection_local_id) { |
| it.key->StartEventLog(output_period_ms); |
| return; |
| } |
| } |
| } |
| |
| void PeerConnectionTracker::StopEventLog(int peer_connection_local_id) { |
| DCHECK_CALLED_ON_VALID_THREAD(main_thread_); |
| for (auto& it : peer_connection_local_id_map_) { |
| if (it.value == peer_connection_local_id) { |
| it.key->StopEventLog(); |
| return; |
| } |
| } |
| } |
| |
| void PeerConnectionTracker::StartDataChannelLog(int peer_connection_local_id) { |
| DCHECK_CALLED_ON_VALID_THREAD(main_thread_); |
| for (auto& it : peer_connection_local_id_map_) { |
| if (it.value == peer_connection_local_id) { |
| it.key->StartDataChannelLog(); |
| return; |
| } |
| } |
| } |
| |
| void PeerConnectionTracker::StopDataChannelLog(int peer_connection_local_id) { |
| DCHECK_CALLED_ON_VALID_THREAD(main_thread_); |
| for (auto& it : peer_connection_local_id_map_) { |
| if (it.value == peer_connection_local_id) { |
| it.key->StopDataChannelLog(); |
| return; |
| } |
| } |
| } |
| |
| void PeerConnectionTracker::GetStandardStats() { |
| DCHECK_CALLED_ON_VALID_THREAD(main_thread_); |
| |
| for (const auto& pair : peer_connection_local_id_map_) { |
| Vector<std::unique_ptr<blink::RTCRtpSenderPlatform>> senders = |
| pair.key->GetPlatformSenders(); |
| webrtc::scoped_refptr<InternalStandardStatsObserver> observer( |
| new webrtc::RefCountedObject<InternalStandardStatsObserver>( |
| pair.key->GetWeakPtr(), pair.value, main_thread_task_runner_, |
| std::move(senders), |
| CrossThreadBindOnce(&PeerConnectionTracker::AddStandardStats, |
| WrapCrossThreadWeakPersistent(this)))); |
| pair.key->GetStandardStatsForTracker(observer); |
| } |
| } |
| |
| void PeerConnectionTracker::GetCurrentState() { |
| DCHECK_CALLED_ON_VALID_THREAD(main_thread_); |
| |
| for (const auto& pair : peer_connection_local_id_map_) { |
| pair.key->EmitCurrentStateForTracker(); |
| } |
| } |
| |
| void PeerConnectionTracker::RegisterPeerConnection( |
| RTCPeerConnectionHandler* pc_handler, |
| const webrtc::PeerConnectionInterface::RTCConfiguration& config, |
| const blink::WebLocalFrame* frame) { |
| DCHECK_CALLED_ON_VALID_THREAD(main_thread_); |
| DCHECK(pc_handler); |
| DCHECK_EQ(GetLocalIDForHandler(pc_handler), -1); |
| DVLOG(1) << "PeerConnectionTracker::RegisterPeerConnection()"; |
| auto info = blink::mojom::blink::PeerConnectionInfo::New(); |
| |
| info->lid = GetNextLocalID(); |
| info->rtc_configuration = |
| SerializeConfiguration(config, pc_handler->encoded_insertable_streams()); |
| |
| if (frame) |
| info->url = frame->GetDocument().Url().GetString(); |
| else |
| info->url = "test:testing"; |
| |
| int32_t lid = info->lid; |
| peer_connection_tracker_host_->AddPeerConnection(std::move(info)); |
| |
| peer_connection_local_id_map_.insert(pc_handler, lid); |
| |
| if (current_thermal_state_ != mojom::blink::DeviceThermalState::kUnknown) { |
| pc_handler->OnThermalStateChange(current_thermal_state_); |
| } |
| } |
| |
| void PeerConnectionTracker::UnregisterPeerConnection( |
| RTCPeerConnectionHandler* pc_handler) { |
| DCHECK_CALLED_ON_VALID_THREAD(main_thread_); |
| DVLOG(1) << "PeerConnectionTracker::UnregisterPeerConnection()"; |
| |
| auto it = peer_connection_local_id_map_.find(pc_handler); |
| |
| if (it == peer_connection_local_id_map_.end()) { |
| // The PeerConnection might not have been registered if its initialization |
| // failed. |
| return; |
| } |
| |
| peer_connection_tracker_host_->RemovePeerConnection(it->value); |
| |
| peer_connection_local_id_map_.erase(it); |
| } |
| |
| void PeerConnectionTracker::TrackCreateOffer( |
| RTCPeerConnectionHandler* pc_handler, |
| RTCOfferOptionsPlatform* options) { |
| DCHECK_CALLED_ON_VALID_THREAD(main_thread_); |
| int id = GetLocalIDForHandler(pc_handler); |
| if (id == -1) |
| return; |
| SendPeerConnectionUpdate( |
| id, "createOffer", |
| StrCat({"options: {", SerializeOfferOptions(options), "}"})); |
| } |
| |
| void PeerConnectionTracker::TrackCreateAnswer( |
| RTCPeerConnectionHandler* pc_handler, |
| RTCAnswerOptionsPlatform* options) { |
| DCHECK_CALLED_ON_VALID_THREAD(main_thread_); |
| int id = GetLocalIDForHandler(pc_handler); |
| if (id == -1) |
| return; |
| SendPeerConnectionUpdate( |
| id, "createAnswer", |
| StrCat({"options: {", SerializeAnswerOptions(options), "}"})); |
| } |
| |
| void PeerConnectionTracker::TrackSetSessionDescription( |
| RTCPeerConnectionHandler* pc_handler, |
| const String& sdp, |
| const String& type, |
| Source source) { |
| DCHECK_CALLED_ON_VALID_THREAD(main_thread_); |
| int id = GetLocalIDForHandler(pc_handler); |
| if (id == -1) |
| return; |
| String value = StrCat({"type: ", type, ", sdp: ", sdp}); |
| SendPeerConnectionUpdate( |
| id, |
| source == kSourceLocal ? "setLocalDescription" : "setRemoteDescription", |
| value); |
| } |
| |
| void PeerConnectionTracker::TrackSetSessionDescriptionImplicit( |
| RTCPeerConnectionHandler* pc_handler) { |
| DCHECK_CALLED_ON_VALID_THREAD(main_thread_); |
| int id = GetLocalIDForHandler(pc_handler); |
| if (id == -1) |
| return; |
| SendPeerConnectionUpdate(id, "setLocalDescription", ""); |
| } |
| |
| void PeerConnectionTracker::TrackSetConfiguration( |
| RTCPeerConnectionHandler* pc_handler, |
| const webrtc::PeerConnectionInterface::RTCConfiguration& config) { |
| DCHECK_CALLED_ON_VALID_THREAD(main_thread_); |
| int id = GetLocalIDForHandler(pc_handler); |
| if (id == -1) |
| return; |
| |
| SendPeerConnectionUpdate( |
| id, "setConfiguration", |
| SerializeConfiguration(config, pc_handler->encoded_insertable_streams())); |
| } |
| |
| void PeerConnectionTracker::TrackAddIceCandidate( |
| RTCPeerConnectionHandler* pc_handler, |
| RTCIceCandidatePlatform* candidate, |
| Source source, |
| bool succeeded) { |
| DCHECK_CALLED_ON_VALID_THREAD(main_thread_); |
| int id = GetLocalIDForHandler(pc_handler); |
| if (id == -1) |
| return; |
| String relay_protocol = candidate->RelayProtocol(); |
| String url = candidate->Url(); |
| String value = StrCat( |
| {"sdpMid: ", String(candidate->SdpMid()), ", ", "sdpMLineIndex: ", |
| (candidate->SdpMLineIndex() ? String::Number(*candidate->SdpMLineIndex()) |
| : "null"), |
| ", candidate: ", String(candidate->Candidate()), |
| (!url.empty() ? ", url: " : ""), (!url.empty() ? url : String()), |
| (!relay_protocol.empty() ? ", relayProtocol: " : ""), |
| (!relay_protocol.empty() ? relay_protocol : String())}); |
| |
| // OnIceCandidate always succeeds as it's a callback from the browser. |
| DCHECK(source != kSourceLocal || succeeded); |
| |
| const char* event = |
| (source == kSourceLocal) |
| ? "icecandidate" |
| : (succeeded ? "addIceCandidate" : "addIceCandidateFailed"); |
| |
| SendPeerConnectionUpdate(id, event, value); |
| } |
| |
| void PeerConnectionTracker::TrackIceCandidateError( |
| RTCPeerConnectionHandler* pc_handler, |
| const String& address, |
| std::optional<uint16_t> port, |
| const String& host_candidate, |
| const String& url, |
| int error_code, |
| const String& error_text) { |
| DCHECK_CALLED_ON_VALID_THREAD(main_thread_); |
| int id = GetLocalIDForHandler(pc_handler); |
| if (id == -1) |
| return; |
| String address_string = |
| address ? StrCat({"address: ", address, "\n"}) : String(); |
| String port_string = |
| port.has_value() ? String::Format("port: %d\n", port.value()) : ""; |
| String value = StrCat({"url: ", url, "\n", address_string, port_string, |
| "host_candidate: ", host_candidate, "\n", |
| "error_text: ", error_text, "\n", |
| "error_code: ", String::Number(error_code)}); |
| SendPeerConnectionUpdate(id, "icecandidateerror", value); |
| } |
| |
| void PeerConnectionTracker::TrackAddTransceiver( |
| RTCPeerConnectionHandler* pc_handler, |
| PeerConnectionTracker::TransceiverUpdatedReason reason, |
| const RTCRtpTransceiverPlatform& transceiver, |
| size_t transceiver_index) { |
| TrackTransceiver("Added", pc_handler, reason, transceiver, transceiver_index); |
| } |
| |
| void PeerConnectionTracker::TrackModifyTransceiver( |
| RTCPeerConnectionHandler* pc_handler, |
| PeerConnectionTracker::TransceiverUpdatedReason reason, |
| const RTCRtpTransceiverPlatform& transceiver, |
| size_t transceiver_index) { |
| TrackTransceiver("Modified", pc_handler, reason, transceiver, |
| transceiver_index); |
| } |
| |
| void PeerConnectionTracker::TrackTransceiver( |
| const char* callback_type_ending, |
| RTCPeerConnectionHandler* pc_handler, |
| PeerConnectionTracker::TransceiverUpdatedReason reason, |
| const RTCRtpTransceiverPlatform& transceiver, |
| size_t transceiver_index) { |
| DCHECK_CALLED_ON_VALID_THREAD(main_thread_); |
| int id = GetLocalIDForHandler(pc_handler); |
| if (id == -1) |
| return; |
| String callback_type = |
| StrCat({"transceiver", String::FromUTF8(callback_type_ending)}); |
| String result = |
| StrCat({"Caused by: ", GetTransceiverUpdatedReasonString(reason), "\n\n", |
| "getTransceivers()", "[", String::Number(transceiver_index), |
| "]:", SerializeTransceiver(transceiver)}); |
| SendPeerConnectionUpdate(id, callback_type, result); |
| } |
| |
| void PeerConnectionTracker::TrackCreateDataChannel( |
| RTCPeerConnectionHandler* pc_handler, |
| const webrtc::DataChannelInterface* data_channel, |
| Source source) { |
| DCHECK_CALLED_ON_VALID_THREAD(main_thread_); |
| int id = GetLocalIDForHandler(pc_handler); |
| if (id == -1) |
| return; |
| // See https://w3c.github.io/webrtc-pc/#dom-rtcdatachannelinit |
| StringBuilder result; |
| result.Append("label: "); |
| result.Append(String::FromUTF8(data_channel->label())); |
| result.Append(", ordered: "); |
| result.Append(String::Boolean(data_channel->ordered())); |
| std::optional<uint16_t> maxPacketLifeTime = data_channel->maxPacketLifeTime(); |
| if (maxPacketLifeTime.has_value()) { |
| result.Append(", maxPacketLifeTime: "); |
| result.Append(String::Number(*maxPacketLifeTime)); |
| } |
| std::optional<uint16_t> maxRetransmits = data_channel->maxRetransmitsOpt(); |
| if (maxRetransmits.has_value()) { |
| result.Append(", maxRetransmits: "); |
| result.Append(String::Number(*maxRetransmits)); |
| } |
| if (!data_channel->protocol().empty()) { |
| result.Append(", protocol: \""); |
| result.Append(String::FromUTF8(data_channel->protocol())); |
| result.Append("\""); |
| } |
| bool negotiated = data_channel->negotiated(); |
| result.Append(", negotiated: "); |
| result.Append(String::Boolean(negotiated)); |
| if (negotiated) { |
| result.Append(", id: "); |
| result.Append(String::Number(data_channel->id())); |
| } |
| // TODO(crbug.com/1455847): add priority |
| // https://w3c.github.io/webrtc-priority/#new-rtcdatachannelinit-member |
| SendPeerConnectionUpdate( |
| id, source == kSourceLocal ? "createDataChannel" : "datachannel", |
| result.ToString()); |
| } |
| |
| void PeerConnectionTracker::TrackClose(RTCPeerConnectionHandler* pc_handler) { |
| DCHECK_CALLED_ON_VALID_THREAD(main_thread_); |
| int id = GetLocalIDForHandler(pc_handler); |
| if (id == -1) |
| return; |
| SendPeerConnectionUpdate(id, "close", g_empty_string); |
| } |
| |
| void PeerConnectionTracker::TrackSignalingStateChange( |
| RTCPeerConnectionHandler* pc_handler, |
| webrtc::PeerConnectionInterface::SignalingState state) { |
| DCHECK_CALLED_ON_VALID_THREAD(main_thread_); |
| int id = GetLocalIDForHandler(pc_handler); |
| if (id == -1) |
| return; |
| SendPeerConnectionUpdate( |
| id, "signalingstatechange", |
| webrtc::PeerConnectionInterface::AsString(state).data()); |
| } |
| |
| void PeerConnectionTracker::TrackIceConnectionStateChange( |
| RTCPeerConnectionHandler* pc_handler, |
| webrtc::PeerConnectionInterface::IceConnectionState state) { |
| DCHECK_CALLED_ON_VALID_THREAD(main_thread_); |
| int id = GetLocalIDForHandler(pc_handler); |
| if (id == -1) |
| return; |
| SendPeerConnectionUpdate( |
| id, "iceconnectionstatechange", |
| webrtc::PeerConnectionInterface::AsString(state).data()); |
| } |
| |
| void PeerConnectionTracker::TrackConnectionStateChange( |
| RTCPeerConnectionHandler* pc_handler, |
| webrtc::PeerConnectionInterface::PeerConnectionState state) { |
| DCHECK_CALLED_ON_VALID_THREAD(main_thread_); |
| int id = GetLocalIDForHandler(pc_handler); |
| if (id == -1) |
| return; |
| SendPeerConnectionUpdate( |
| id, "connectionstatechange", |
| webrtc::PeerConnectionInterface::AsString(state).data()); |
| } |
| |
| void PeerConnectionTracker::TrackIceGatheringStateChange( |
| RTCPeerConnectionHandler* pc_handler, |
| webrtc::PeerConnectionInterface::IceGatheringState state) { |
| DCHECK_CALLED_ON_VALID_THREAD(main_thread_); |
| int id = GetLocalIDForHandler(pc_handler); |
| if (id == -1) |
| return; |
| SendPeerConnectionUpdate( |
| id, "icegatheringstatechange", |
| webrtc::PeerConnectionInterface::AsString(state).data()); |
| } |
| |
| void PeerConnectionTracker::TrackSessionDescriptionCallback( |
| RTCPeerConnectionHandler* pc_handler, |
| Action action, |
| const String& callback_type, |
| const String& value) { |
| DCHECK_CALLED_ON_VALID_THREAD(main_thread_); |
| int id = GetLocalIDForHandler(pc_handler); |
| if (id == -1) |
| return; |
| String update_type; |
| switch (action) { |
| case kActionSetLocalDescription: |
| update_type = "setLocalDescription"; |
| break; |
| case kActionSetLocalDescriptionImplicit: |
| update_type = "setLocalDescription"; |
| break; |
| case kActionSetRemoteDescription: |
| update_type = "setRemoteDescription"; |
| break; |
| case kActionCreateOffer: |
| update_type = "createOffer"; |
| break; |
| case kActionCreateAnswer: |
| update_type = "createAnswer"; |
| break; |
| default: |
| NOTREACHED(); |
| } |
| update_type = StrCat({update_type, callback_type}); |
| |
| SendPeerConnectionUpdate(id, update_type, value); |
| } |
| |
| void PeerConnectionTracker::TrackSessionId(RTCPeerConnectionHandler* pc_handler, |
| const String& session_id) { |
| DCHECK_CALLED_ON_VALID_THREAD(main_thread_); |
| DCHECK(pc_handler); |
| DCHECK(!session_id.empty()); |
| const int local_id = GetLocalIDForHandler(pc_handler); |
| if (local_id == -1) { |
| return; |
| } |
| |
| String non_null_session_id = |
| session_id.IsNull() ? g_empty_string : session_id; |
| peer_connection_tracker_host_->OnPeerConnectionSessionIdSet( |
| local_id, non_null_session_id); |
| } |
| |
| void PeerConnectionTracker::TrackOnRenegotiationNeeded( |
| RTCPeerConnectionHandler* pc_handler) { |
| DCHECK_CALLED_ON_VALID_THREAD(main_thread_); |
| int id = GetLocalIDForHandler(pc_handler); |
| if (id == -1) |
| return; |
| SendPeerConnectionUpdate(id, "negotiationneeded", g_empty_string); |
| } |
| |
| void PeerConnectionTracker::TrackGetUserMedia( |
| UserMediaRequest* user_media_request) { |
| DCHECK_CALLED_ON_VALID_THREAD(main_thread_); |
| |
| peer_connection_tracker_host_->GetUserMedia( |
| user_media_request->request_id(), user_media_request->Audio(), |
| user_media_request->Video(), |
| SerializeGetUserMediaMediaConstraints( |
| user_media_request->AudioConstraints()), |
| SerializeGetUserMediaMediaConstraints( |
| user_media_request->VideoConstraints())); |
| } |
| |
| void PeerConnectionTracker::TrackGetUserMediaSuccess( |
| UserMediaRequest* user_media_request, |
| const MediaStream* stream) { |
| DCHECK_CALLED_ON_VALID_THREAD(main_thread_); |
| |
| // Serialize audio and video track information (id and label) or an |
| // empty string when there is no such track. |
| String audio_track_info = |
| stream->getAudioTracks().empty() |
| ? g_empty_string |
| : StrCat({"id:", stream->getAudioTracks()[0]->id(), |
| " label:", stream->getAudioTracks()[0]->label()}); |
| String video_track_info = |
| stream->getVideoTracks().empty() |
| ? g_empty_string |
| : StrCat({"id:", stream->getVideoTracks()[0]->id(), |
| " label:", stream->getVideoTracks()[0]->label()}); |
| |
| peer_connection_tracker_host_->GetUserMediaSuccess( |
| user_media_request->request_id(), stream->id(), audio_track_info, |
| video_track_info); |
| } |
| |
| void PeerConnectionTracker::TrackGetUserMediaFailure( |
| UserMediaRequest* user_media_request, |
| const String& error, |
| const String& error_message) { |
| DCHECK_CALLED_ON_VALID_THREAD(main_thread_); |
| |
| peer_connection_tracker_host_->GetUserMediaFailure( |
| user_media_request->request_id(), error, error_message); |
| } |
| |
| void PeerConnectionTracker::TrackGetDisplayMedia( |
| UserMediaRequest* user_media_request) { |
| DCHECK_CALLED_ON_VALID_THREAD(main_thread_); |
| |
| peer_connection_tracker_host_->GetDisplayMedia( |
| user_media_request->request_id(), user_media_request->Audio(), |
| user_media_request->Video(), |
| SerializeGetUserMediaMediaConstraints( |
| user_media_request->AudioConstraints()), |
| SerializeGetUserMediaMediaConstraints( |
| user_media_request->VideoConstraints())); |
| } |
| |
| void PeerConnectionTracker::TrackGetDisplayMediaSuccess( |
| UserMediaRequest* user_media_request, |
| MediaStream* stream) { |
| DCHECK_CALLED_ON_VALID_THREAD(main_thread_); |
| |
| // Serialize audio and video track information (id and label) or an |
| // empty string when there is no such track. |
| String audio_track_info = |
| stream->getAudioTracks().empty() |
| ? g_empty_string |
| : StrCat({"id:", stream->getAudioTracks()[0]->id(), |
| " label:", stream->getAudioTracks()[0]->label()}); |
| String video_track_info = |
| stream->getVideoTracks().empty() |
| ? g_empty_string |
| : StrCat({"id:", stream->getVideoTracks()[0]->id(), |
| " label:", stream->getVideoTracks()[0]->label()}); |
| |
| peer_connection_tracker_host_->GetDisplayMediaSuccess( |
| user_media_request->request_id(), stream->id(), audio_track_info, |
| video_track_info); |
| } |
| |
| void PeerConnectionTracker::TrackGetDisplayMediaFailure( |
| UserMediaRequest* user_media_request, |
| const String& error, |
| const String& error_message) { |
| DCHECK_CALLED_ON_VALID_THREAD(main_thread_); |
| |
| peer_connection_tracker_host_->GetDisplayMediaFailure( |
| user_media_request->request_id(), error, error_message); |
| } |
| |
| void PeerConnectionTracker::TrackRtcEventLogWrite( |
| RTCPeerConnectionHandler* pc_handler, |
| const Vector<uint8_t>& output) { |
| DCHECK_CALLED_ON_VALID_THREAD(main_thread_); |
| int id = GetLocalIDForHandler(pc_handler); |
| if (id == -1) |
| return; |
| |
| peer_connection_tracker_host_->WebRtcEventLogWrite(id, output); |
| } |
| |
| void PeerConnectionTracker::TrackRtcDataChannelLogWrite( |
| RTCPeerConnectionHandler* pc_handler, |
| const Vector<uint8_t>& output) { |
| DCHECK_CALLED_ON_VALID_THREAD(main_thread_); |
| int id = GetLocalIDForHandler(pc_handler); |
| if (id == -1) { |
| return; |
| } |
| |
| peer_connection_tracker_host_->WebRtcDataChannelLogWrite(id, output); |
| } |
| |
| int PeerConnectionTracker::GetNextLocalID() { |
| DCHECK_CALLED_ON_VALID_THREAD(main_thread_); |
| return GetNextProcessLocalID(); |
| } |
| |
| int PeerConnectionTracker::GetLocalIDForHandler( |
| RTCPeerConnectionHandler* handler) const { |
| DCHECK_CALLED_ON_VALID_THREAD(main_thread_); |
| const auto found = peer_connection_local_id_map_.find(handler); |
| if (found == peer_connection_local_id_map_.end()) { |
| return -1; |
| } |
| DCHECK_NE(found->value, -1); |
| return found->value; |
| } |
| |
| void PeerConnectionTracker::SendPeerConnectionUpdate( |
| int local_id, |
| const String& callback_type, |
| const String& value) { |
| DCHECK_CALLED_ON_VALID_THREAD(main_thread_); |
| peer_connection_tracker_host_->UpdatePeerConnection(local_id, callback_type, |
| value); |
| } |
| |
| void PeerConnectionTracker::AddStandardStats(int lid, base::Value::List value) { |
| peer_connection_tracker_host_->AddStandardStats(lid, std::move(value)); |
| } |
| |
| } // namespace blink |