blob: 7df20909aab56a66d6f3a403dfa4e7ce1acc7c24 [file] [log] [blame]
henrike@webrtc.org28e20752013-07-10 00:45:36 +00001/*
2 * libjingle
jlmiller@webrtc.org5f93d0a2015-01-20 21:36:13 +00003 * Copyright 2012 Google Inc.
henrike@webrtc.org28e20752013-07-10 00:45:36 +00004 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright notice,
11 * this list of conditions and the following disclaimer in the documentation
12 * and/or other materials provided with the distribution.
13 * 3. The name of the author may not be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28#include "talk/examples/peerconnection/client/conductor.h"
29
30#include <utility>
braveyao@webrtc.orgc68e0c92015-02-27 09:51:25 +000031#include <vector>
henrike@webrtc.org28e20752013-07-10 00:45:36 +000032
33#include "talk/app/webrtc/videosourceinterface.h"
buildbot@webrtc.orga09a9992014-08-13 17:26:08 +000034#include "talk/examples/peerconnection/client/defaults.h"
35#include "talk/media/devices/devicemanager.h"
braveyao@webrtc.orga742cb12015-01-29 04:23:01 +000036#include "talk/app/webrtc/test/fakeconstraints.h"
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +000037#include "webrtc/base/common.h"
38#include "webrtc/base/json.h"
39#include "webrtc/base/logging.h"
henrike@webrtc.org28e20752013-07-10 00:45:36 +000040
41// Names used for a IceCandidate JSON object.
42const char kCandidateSdpMidName[] = "sdpMid";
43const char kCandidateSdpMlineIndexName[] = "sdpMLineIndex";
44const char kCandidateSdpName[] = "candidate";
45
46// Names used for a SessionDescription JSON object.
47const char kSessionDescriptionTypeName[] = "type";
48const char kSessionDescriptionSdpName[] = "sdp";
49
braveyao@webrtc.orga742cb12015-01-29 04:23:01 +000050#define DTLS_ON true
51#define DTLS_OFF false
52
henrike@webrtc.org28e20752013-07-10 00:45:36 +000053class DummySetSessionDescriptionObserver
54 : public webrtc::SetSessionDescriptionObserver {
55 public:
56 static DummySetSessionDescriptionObserver* Create() {
57 return
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +000058 new rtc::RefCountedObject<DummySetSessionDescriptionObserver>();
henrike@webrtc.org28e20752013-07-10 00:45:36 +000059 }
60 virtual void OnSuccess() {
61 LOG(INFO) << __FUNCTION__;
62 }
63 virtual void OnFailure(const std::string& error) {
64 LOG(INFO) << __FUNCTION__ << " " << error;
65 }
66
67 protected:
68 DummySetSessionDescriptionObserver() {}
69 ~DummySetSessionDescriptionObserver() {}
70};
71
72Conductor::Conductor(PeerConnectionClient* client, MainWindow* main_wnd)
73 : peer_id_(-1),
braveyao@webrtc.orga742cb12015-01-29 04:23:01 +000074 loopback_(false),
henrike@webrtc.org28e20752013-07-10 00:45:36 +000075 client_(client),
76 main_wnd_(main_wnd) {
77 client_->RegisterObserver(this);
78 main_wnd->RegisterObserver(this);
79}
80
81Conductor::~Conductor() {
82 ASSERT(peer_connection_.get() == NULL);
83}
84
85bool Conductor::connection_active() const {
86 return peer_connection_.get() != NULL;
87}
88
89void Conductor::Close() {
90 client_->SignOut();
91 DeletePeerConnection();
92}
93
94bool Conductor::InitializePeerConnection() {
95 ASSERT(peer_connection_factory_.get() == NULL);
96 ASSERT(peer_connection_.get() == NULL);
97
98 peer_connection_factory_ = webrtc::CreatePeerConnectionFactory();
99
100 if (!peer_connection_factory_.get()) {
101 main_wnd_->MessageBox("Error",
102 "Failed to initialize PeerConnectionFactory", true);
103 DeletePeerConnection();
104 return false;
105 }
106
braveyao@webrtc.orgc68e0c92015-02-27 09:51:25 +0000107 if (!CreatePeerConnection(DTLS_ON)) {
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000108 main_wnd_->MessageBox("Error",
109 "CreatePeerConnection failed", true);
110 DeletePeerConnection();
111 }
112 AddStreams();
113 return peer_connection_.get() != NULL;
114}
115
braveyao@webrtc.orga742cb12015-01-29 04:23:01 +0000116bool Conductor::ReinitializePeerConnectionForLoopback() {
braveyao@webrtc.orgc68e0c92015-02-27 09:51:25 +0000117 loopback_ = true;
118 rtc::scoped_refptr<webrtc::StreamCollectionInterface> streams(
119 peer_connection_->local_streams());
120 peer_connection_ = NULL;
braveyao@webrtc.orga742cb12015-01-29 04:23:01 +0000121 if (CreatePeerConnection(DTLS_OFF)) {
braveyao@webrtc.orgc68e0c92015-02-27 09:51:25 +0000122 for (size_t i = 0; i < streams->count(); ++i)
123 peer_connection_->AddStream(streams->at(i));
124 peer_connection_->CreateOffer(this, NULL);
braveyao@webrtc.orga742cb12015-01-29 04:23:01 +0000125 }
126 return peer_connection_.get() != NULL;
127}
128
129bool Conductor::CreatePeerConnection(bool dtls) {
130 ASSERT(peer_connection_factory_.get() != NULL);
131 ASSERT(peer_connection_.get() == NULL);
132
braveyao@webrtc.orgc68e0c92015-02-27 09:51:25 +0000133 webrtc::PeerConnectionInterface::IceServers servers;
134 webrtc::PeerConnectionInterface::IceServer server;
135 server.uri = GetPeerConnectionString();
136 servers.push_back(server);
137
braveyao@webrtc.orga742cb12015-01-29 04:23:01 +0000138 webrtc::FakeConstraints constraints;
139 if (dtls) {
braveyao@webrtc.orgc68e0c92015-02-27 09:51:25 +0000140 constraints.AddOptional(webrtc::MediaConstraintsInterface::kEnableDtlsSrtp,
141 "true");
142 }
143
144 peer_connection_ =
145 peer_connection_factory_->CreatePeerConnection(servers,
146 &constraints,
147 NULL,
148 NULL,
149 this);
braveyao@webrtc.orga742cb12015-01-29 04:23:01 +0000150 return peer_connection_.get() != NULL;
151}
152
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000153void Conductor::DeletePeerConnection() {
154 peer_connection_ = NULL;
155 active_streams_.clear();
156 main_wnd_->StopLocalRenderer();
157 main_wnd_->StopRemoteRenderer();
158 peer_connection_factory_ = NULL;
159 peer_id_ = -1;
braveyao@webrtc.orga742cb12015-01-29 04:23:01 +0000160 loopback_ = false;
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000161}
162
163void Conductor::EnsureStreamingUI() {
164 ASSERT(peer_connection_.get() != NULL);
165 if (main_wnd_->IsWindow()) {
166 if (main_wnd_->current_ui() != MainWindow::STREAMING)
167 main_wnd_->SwitchToStreamingUI();
168 }
169}
170
171//
172// PeerConnectionObserver implementation.
173//
174
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000175// Called when a remote stream is added
176void Conductor::OnAddStream(webrtc::MediaStreamInterface* stream) {
177 LOG(INFO) << __FUNCTION__ << " " << stream->label();
178
179 stream->AddRef();
180 main_wnd_->QueueUIThreadCallback(NEW_STREAM_ADDED,
181 stream);
182}
183
184void Conductor::OnRemoveStream(webrtc::MediaStreamInterface* stream) {
185 LOG(INFO) << __FUNCTION__ << " " << stream->label();
186 stream->AddRef();
187 main_wnd_->QueueUIThreadCallback(STREAM_REMOVED,
188 stream);
189}
190
191void Conductor::OnIceCandidate(const webrtc::IceCandidateInterface* candidate) {
192 LOG(INFO) << __FUNCTION__ << " " << candidate->sdp_mline_index();
braveyao@webrtc.orgc68e0c92015-02-27 09:51:25 +0000193 // For loopback test. To save some connecting delay.
194 if (loopback_) {
195 if (!peer_connection_->AddIceCandidate(candidate)) {
196 LOG(WARNING) << "Failed to apply the received candidate";
197 }
198 return;
braveyao@webrtc.orga742cb12015-01-29 04:23:01 +0000199 }
200
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000201 Json::StyledWriter writer;
202 Json::Value jmessage;
203
204 jmessage[kCandidateSdpMidName] = candidate->sdp_mid();
205 jmessage[kCandidateSdpMlineIndexName] = candidate->sdp_mline_index();
206 std::string sdp;
207 if (!candidate->ToString(&sdp)) {
208 LOG(LS_ERROR) << "Failed to serialize candidate";
209 return;
210 }
211 jmessage[kCandidateSdpName] = sdp;
212 SendMessage(writer.write(jmessage));
213}
214
215//
216// PeerConnectionClientObserver implementation.
217//
218
219void Conductor::OnSignedIn() {
220 LOG(INFO) << __FUNCTION__;
221 main_wnd_->SwitchToPeerList(client_->peers());
222}
223
224void Conductor::OnDisconnected() {
225 LOG(INFO) << __FUNCTION__;
226
227 DeletePeerConnection();
228
229 if (main_wnd_->IsWindow())
230 main_wnd_->SwitchToConnectUI();
231}
232
233void Conductor::OnPeerConnected(int id, const std::string& name) {
234 LOG(INFO) << __FUNCTION__;
235 // Refresh the list if we're showing it.
236 if (main_wnd_->current_ui() == MainWindow::LIST_PEERS)
237 main_wnd_->SwitchToPeerList(client_->peers());
238}
239
240void Conductor::OnPeerDisconnected(int id) {
241 LOG(INFO) << __FUNCTION__;
242 if (id == peer_id_) {
243 LOG(INFO) << "Our peer disconnected";
244 main_wnd_->QueueUIThreadCallback(PEER_CONNECTION_CLOSED, NULL);
245 } else {
246 // Refresh the list if we're showing it.
247 if (main_wnd_->current_ui() == MainWindow::LIST_PEERS)
248 main_wnd_->SwitchToPeerList(client_->peers());
249 }
250}
251
252void Conductor::OnMessageFromPeer(int peer_id, const std::string& message) {
253 ASSERT(peer_id_ == peer_id || peer_id_ == -1);
254 ASSERT(!message.empty());
255
256 if (!peer_connection_.get()) {
257 ASSERT(peer_id_ == -1);
258 peer_id_ = peer_id;
259
260 if (!InitializePeerConnection()) {
261 LOG(LS_ERROR) << "Failed to initialize our PeerConnection instance";
262 client_->SignOut();
263 return;
264 }
265 } else if (peer_id != peer_id_) {
266 ASSERT(peer_id_ != -1);
267 LOG(WARNING) << "Received a message from unknown peer while already in a "
268 "conversation with a different peer.";
269 return;
270 }
271
272 Json::Reader reader;
273 Json::Value jmessage;
274 if (!reader.parse(message, jmessage)) {
275 LOG(WARNING) << "Received unknown message. " << message;
276 return;
277 }
278 std::string type;
279 std::string json_object;
280
281 GetStringFromJsonObject(jmessage, kSessionDescriptionTypeName, &type);
282 if (!type.empty()) {
braveyao@webrtc.orgc68e0c92015-02-27 09:51:25 +0000283 if (type == "offer-loopback") {
284 // This is a loopback call.
285 // Recreate the peerconnection with DTLS disabled.
286 if (!ReinitializePeerConnectionForLoopback()) {
braveyao@webrtc.orga742cb12015-01-29 04:23:01 +0000287 LOG(LS_ERROR) << "Failed to initialize our PeerConnection instance";
288 DeletePeerConnection();
braveyao@webrtc.orgc68e0c92015-02-27 09:51:25 +0000289 client_->SignOut();
290 }
291 return;
braveyao@webrtc.orga742cb12015-01-29 04:23:01 +0000292 }
293
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000294 std::string sdp;
295 if (!GetStringFromJsonObject(jmessage, kSessionDescriptionSdpName, &sdp)) {
296 LOG(WARNING) << "Can't parse received session description message.";
297 return;
298 }
299 webrtc::SessionDescriptionInterface* session_description(
300 webrtc::CreateSessionDescription(type, sdp));
301 if (!session_description) {
302 LOG(WARNING) << "Can't parse received session description message.";
303 return;
304 }
305 LOG(INFO) << " Received session description :" << message;
306 peer_connection_->SetRemoteDescription(
307 DummySetSessionDescriptionObserver::Create(), session_description);
308 if (session_description->type() ==
309 webrtc::SessionDescriptionInterface::kOffer) {
310 peer_connection_->CreateAnswer(this, NULL);
311 }
312 return;
313 } else {
314 std::string sdp_mid;
315 int sdp_mlineindex = 0;
316 std::string sdp;
317 if (!GetStringFromJsonObject(jmessage, kCandidateSdpMidName, &sdp_mid) ||
318 !GetIntFromJsonObject(jmessage, kCandidateSdpMlineIndexName,
319 &sdp_mlineindex) ||
320 !GetStringFromJsonObject(jmessage, kCandidateSdpName, &sdp)) {
321 LOG(WARNING) << "Can't parse received message.";
322 return;
323 }
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000324 rtc::scoped_ptr<webrtc::IceCandidateInterface> candidate(
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000325 webrtc::CreateIceCandidate(sdp_mid, sdp_mlineindex, sdp));
326 if (!candidate.get()) {
327 LOG(WARNING) << "Can't parse received candidate message.";
328 return;
329 }
330 if (!peer_connection_->AddIceCandidate(candidate.get())) {
331 LOG(WARNING) << "Failed to apply the received candidate";
332 return;
333 }
334 LOG(INFO) << " Received candidate :" << message;
335 return;
336 }
337}
338
339void Conductor::OnMessageSent(int err) {
340 // Process the next pending message if any.
341 main_wnd_->QueueUIThreadCallback(SEND_MESSAGE_TO_PEER, NULL);
342}
343
344void Conductor::OnServerConnectionFailure() {
345 main_wnd_->MessageBox("Error", ("Failed to connect to " + server_).c_str(),
346 true);
347}
348
349//
350// MainWndCallback implementation.
351//
352
353void Conductor::StartLogin(const std::string& server, int port) {
354 if (client_->is_connected())
355 return;
356 server_ = server;
357 client_->Connect(server, port, GetPeerName());
358}
359
360void Conductor::DisconnectFromServer() {
361 if (client_->is_connected())
362 client_->SignOut();
363}
364
365void Conductor::ConnectToPeer(int peer_id) {
366 ASSERT(peer_id_ == -1);
367 ASSERT(peer_id != -1);
368
369 if (peer_connection_.get()) {
370 main_wnd_->MessageBox("Error",
371 "We only support connecting to one peer at a time", true);
372 return;
373 }
374
375 if (InitializePeerConnection()) {
376 peer_id_ = peer_id;
377 peer_connection_->CreateOffer(this, NULL);
378 } else {
379 main_wnd_->MessageBox("Error", "Failed to initialize PeerConnection", true);
380 }
381}
382
383cricket::VideoCapturer* Conductor::OpenVideoCaptureDevice() {
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000384 rtc::scoped_ptr<cricket::DeviceManagerInterface> dev_manager(
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000385 cricket::DeviceManagerFactory::Create());
386 if (!dev_manager->Init()) {
387 LOG(LS_ERROR) << "Can't create device manager";
388 return NULL;
389 }
390 std::vector<cricket::Device> devs;
391 if (!dev_manager->GetVideoCaptureDevices(&devs)) {
392 LOG(LS_ERROR) << "Can't enumerate video devices";
393 return NULL;
394 }
395 std::vector<cricket::Device>::iterator dev_it = devs.begin();
396 cricket::VideoCapturer* capturer = NULL;
397 for (; dev_it != devs.end(); ++dev_it) {
398 capturer = dev_manager->CreateVideoCapturer(*dev_it);
399 if (capturer != NULL)
400 break;
401 }
402 return capturer;
403}
404
405void Conductor::AddStreams() {
406 if (active_streams_.find(kStreamLabel) != active_streams_.end())
407 return; // Already added.
408
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000409 rtc::scoped_refptr<webrtc::AudioTrackInterface> audio_track(
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000410 peer_connection_factory_->CreateAudioTrack(
411 kAudioLabel, peer_connection_factory_->CreateAudioSource(NULL)));
412
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000413 rtc::scoped_refptr<webrtc::VideoTrackInterface> video_track(
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000414 peer_connection_factory_->CreateVideoTrack(
415 kVideoLabel,
416 peer_connection_factory_->CreateVideoSource(OpenVideoCaptureDevice(),
417 NULL)));
418 main_wnd_->StartLocalRenderer(video_track);
419
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000420 rtc::scoped_refptr<webrtc::MediaStreamInterface> stream =
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000421 peer_connection_factory_->CreateLocalMediaStream(kStreamLabel);
422
423 stream->AddTrack(audio_track);
424 stream->AddTrack(video_track);
perkj@webrtc.orgc2dd5ee2014-11-04 11:31:29 +0000425 if (!peer_connection_->AddStream(stream)) {
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000426 LOG(LS_ERROR) << "Adding stream to PeerConnection failed";
427 }
428 typedef std::pair<std::string,
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000429 rtc::scoped_refptr<webrtc::MediaStreamInterface> >
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000430 MediaStreamPair;
431 active_streams_.insert(MediaStreamPair(stream->label(), stream));
432 main_wnd_->SwitchToStreamingUI();
433}
434
435void Conductor::DisconnectFromCurrentPeer() {
436 LOG(INFO) << __FUNCTION__;
437 if (peer_connection_.get()) {
438 client_->SendHangUp(peer_id_);
439 DeletePeerConnection();
440 }
441
442 if (main_wnd_->IsWindow())
443 main_wnd_->SwitchToPeerList(client_->peers());
444}
445
446void Conductor::UIThreadCallback(int msg_id, void* data) {
447 switch (msg_id) {
448 case PEER_CONNECTION_CLOSED:
449 LOG(INFO) << "PEER_CONNECTION_CLOSED";
450 DeletePeerConnection();
451
452 ASSERT(active_streams_.empty());
453
454 if (main_wnd_->IsWindow()) {
455 if (client_->is_connected()) {
456 main_wnd_->SwitchToPeerList(client_->peers());
457 } else {
458 main_wnd_->SwitchToConnectUI();
459 }
460 } else {
461 DisconnectFromServer();
462 }
463 break;
464
465 case SEND_MESSAGE_TO_PEER: {
466 LOG(INFO) << "SEND_MESSAGE_TO_PEER";
467 std::string* msg = reinterpret_cast<std::string*>(data);
468 if (msg) {
469 // For convenience, we always run the message through the queue.
470 // This way we can be sure that messages are sent to the server
471 // in the same order they were signaled without much hassle.
472 pending_messages_.push_back(msg);
473 }
474
475 if (!pending_messages_.empty() && !client_->IsSendingMessage()) {
476 msg = pending_messages_.front();
477 pending_messages_.pop_front();
478
479 if (!client_->SendToPeer(peer_id_, *msg) && peer_id_ != -1) {
480 LOG(LS_ERROR) << "SendToPeer failed";
481 DisconnectFromServer();
482 }
483 delete msg;
484 }
485
486 if (!peer_connection_.get())
487 peer_id_ = -1;
488
489 break;
490 }
491
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000492 case NEW_STREAM_ADDED: {
493 webrtc::MediaStreamInterface* stream =
494 reinterpret_cast<webrtc::MediaStreamInterface*>(
495 data);
496 webrtc::VideoTrackVector tracks = stream->GetVideoTracks();
497 // Only render the first track.
498 if (!tracks.empty()) {
499 webrtc::VideoTrackInterface* track = tracks[0];
500 main_wnd_->StartRemoteRenderer(track);
501 }
502 stream->Release();
503 break;
504 }
505
506 case STREAM_REMOVED: {
507 // Remote peer stopped sending a stream.
508 webrtc::MediaStreamInterface* stream =
509 reinterpret_cast<webrtc::MediaStreamInterface*>(
510 data);
511 stream->Release();
512 break;
513 }
514
515 default:
516 ASSERT(false);
517 break;
518 }
519}
520
521void Conductor::OnSuccess(webrtc::SessionDescriptionInterface* desc) {
braveyao@webrtc.orgc68e0c92015-02-27 09:51:25 +0000522 peer_connection_->SetLocalDescription(
523 DummySetSessionDescriptionObserver::Create(), desc);
524
braveyao@webrtc.orga742cb12015-01-29 04:23:01 +0000525 std::string sdp;
braveyao@webrtc.orgc68e0c92015-02-27 09:51:25 +0000526 desc->ToString(&sdp);
527
528 // For loopback test. To save some connecting delay.
529 if (loopback_) {
530 // Replace message type from "offer" to "answer"
531 webrtc::SessionDescriptionInterface* session_description(
532 webrtc::CreateSessionDescription("answer", sdp));
533 peer_connection_->SetRemoteDescription(
534 DummySetSessionDescriptionObserver::Create(), session_description);
535 return;
536 }
braveyao@webrtc.orga742cb12015-01-29 04:23:01 +0000537
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000538 Json::StyledWriter writer;
539 Json::Value jmessage;
540 jmessage[kSessionDescriptionTypeName] = desc->type();
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000541 jmessage[kSessionDescriptionSdpName] = sdp;
542 SendMessage(writer.write(jmessage));
543}
544
545void Conductor::OnFailure(const std::string& error) {
546 LOG(LERROR) << error;
547}
548
549void Conductor::SendMessage(const std::string& json_object) {
550 std::string* msg = new std::string(json_object);
551 main_wnd_->QueueUIThreadCallback(SEND_MESSAGE_TO_PEER, msg);
552}