Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1180)

Unified Diff: content/renderer/media/media_stream_impl.cc

Issue 8060055: Adding support for MediaStream and PeerConnection functionality (Closed) Base URL: http://git.chromium.org/chromium/chromium.git@trunk
Patch Set: Code review, adding dependency factory, adding unit test, misc updates Created 9 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: content/renderer/media/media_stream_impl.cc
diff --git a/content/renderer/media/media_stream_impl.cc b/content/renderer/media/media_stream_impl.cc
index 52e9ec2eaaed9f442477afabad0ce93110abc9d5..246a32057fd32329a628cd24e18ff46a7734a7f7 100644
--- a/content/renderer/media/media_stream_impl.cc
+++ b/content/renderer/media/media_stream_impl.cc
@@ -4,12 +4,40 @@
#include "content/renderer/media/media_stream_impl.h"
-#include "base/string_util.h"
+#include <vector>
+
+#include "base/logging.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/utf_string_conversions.h"
+#include "content/common/media/media_stream_options.h"
#include "content/renderer/media/capture_video_decoder.h"
+#include "content/renderer/media/media_stream_dependency_factory.h"
+#include "content/renderer/media/media_stream_dispatcher.h"
+#include "content/renderer/media/rtc_video_decoder.h"
#include "content/renderer/media/video_capture_impl_manager.h"
-#include "googleurl/src/gurl.h"
+#include "content/renderer/media/video_capture_module_impl.h"
+#include "content/renderer/media/webrtc_audio_device_impl.h"
+#include "content/renderer/p2p/ipc_network_manager.h"
+#include "content/renderer/p2p/ipc_socket_factory.h"
+#include "content/renderer/p2p/socket_dispatcher.h"
+#include "jingle/glue/thread_wrapper.h"
#include "media/base/message_loop_factory.h"
-#include "media/base/pipeline.h"
+#include "third_party/libjingle/source/talk/p2p/client/httpportallocator.h"
+#include "third_party/libjingle/source/talk/session/phone/dummydevicemanager.h"
+#include "third_party/libjingle/source/talk/session/phone/webrtcmediaengine.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebKit.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebLocalMediaStream.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebMediaStream.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebMediaStreamController.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebMediaStreamRegistry.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebMediaStreamTrack.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebMediaStreamTrackList.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebSecurityOrigin.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebString.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebVector.h"
+
+// TODO(grunell): Replace NewRunnableMethod with base::Bind throughout this
+// class.
namespace {
@@ -17,24 +45,300 @@ static const int kVideoCaptureWidth = 352;
static const int kVideoCaptureHeight = 288;
static const int kVideoCaptureFramePerSecond = 30;
-static const int kStartOpenSessionId = 1;
+} // namespace
-// TODO(wjia): remove this string when full media stream code is checked in.
-static const char kRawMediaScheme[] = "mediastream";
+MediaStreamImpl::MediaStreamImpl(
+ MediaStreamDispatcher* media_stream_dispatcher,
+ content::P2PSocketDispatcher* p2p_socket_dispatcher,
+ VideoCaptureImplManager* vc_manager,
+ MediaStreamDependencyFactory* dependency_factory)
+ : dependency_factory_(dependency_factory),
+ media_stream_dispatcher_(media_stream_dispatcher),
+ media_engine_(NULL),
+ p2p_socket_dispatcher_(p2p_socket_dispatcher),
+ vc_manager_(vc_manager),
+ signaling_thread_(NULL),
+ worker_thread_(NULL),
+ chrome_worker_thread_("Chrome_libJingle_WorkerThread"),
+ call_state_(NOT_STARTED),
+ vcm_created_(false) {
+ message_loop_proxy_ = base::MessageLoopProxy::current();
+}
-} // namespace
+MediaStreamImpl::~MediaStreamImpl() {
+ if (native_peer_connection_.get()) {
+ native_peer_connection_->RegisterObserver(NULL);
+ native_peer_connection_->Close();
+ }
+ dependency_factory_->DeletePeerConnectionFactory();
+}
+
+void MediaStreamImpl::setController(
+ WebKit::WebMediaStreamController* controller) {
+ controller_.reset(controller);
+}
+
+void MediaStreamImpl::shutdown() {
+ controller_.reset();
+}
+
+void MediaStreamImpl::generateStream(
+ int request_id,
+ WebKit::WebGenerateStreamOptionFlags flags,
+ const WebKit::WebSecurityOrigin& web_security_origin) {
+ bool audio = (flags & WebKit::WebGenerateStreamRequestAudio) != 0;
+ media_stream::StreamOptions::VideoOption video_option =
+ media_stream::StreamOptions::kNoCamera;
+ if ((flags & WebKit::WebGenerateStreamRequestVideoFacingUser) &&
+ (flags & WebKit::WebGenerateStreamRequestVideoFacingEnvironment)) {
+ video_option = media_stream::StreamOptions::kFacingBoth;
+ } else {
+ if (flags & WebKit::WebGenerateStreamRequestVideoFacingEnvironment)
+ video_option = media_stream::StreamOptions::kFacingEnvironment;
+ if (flags & WebKit::WebGenerateStreamRequestVideoFacingUser)
+ video_option = media_stream::StreamOptions::kFacingUser;
+ }
+ DLOG(INFO) << "MediaStreamImpl::generateStream("
+ << request_id << ", [ "
+ << (audio ? "audio " : "")
+ << ((flags & WebKit::WebGenerateStreamRequestVideoFacingUser) ?
+ "video_facing_user " : "")
+ << ((flags &
+ WebKit::WebGenerateStreamRequestVideoFacingEnvironment) ?
+ "video_facing_environment " : "")
+ << "], "
+ << static_cast<string16>(web_security_origin.toString()) << ")";
+
+ media_stream_dispatcher_->GenerateStream(request_id, this,
+ media_stream::StreamOptions(audio, video_option),
+ UTF16ToUTF8(web_security_origin.toString()));
+}
+
+void MediaStreamImpl::stopGeneratedStream(
+ const WebKit::WebLocalMediaStream& stream) {
+ std::string label = UTF16ToUTF8(stream.label());
+ media_stream_dispatcher_->StopStream(label);
+}
+
+void MediaStreamImpl::recordStream(
+ const WebKit::WebMediaStreamRecorder& recorder) {
+ // TODO(grunell): Implement.
+}
+
+void MediaStreamImpl::getRecordedData(
+ const WebKit::WebMediaStreamRecorder& recorder,
+ int request_id) {
+ // TODO(grunell): Implement.
+}
+
+void MediaStreamImpl::disposeRecordedData(
+ const WebKit::WebMediaStreamRecorder& recorder) {
+ // TODO(grunell): Implement.
+}
+
+void MediaStreamImpl::setMediaStreamTrackEnabled(
+ const WebKit::WebMediaStreamTrack& track) {
+ // TODO(grunell): Implement.
+}
+
+void MediaStreamImpl::processSignalingMessage(
+ const WebKit::WebPeerConnection& web_peer_connection,
+ const WebKit::WebString& message) {
+ if (web_peer_connection != web_peer_connection_) {
+ DLOG(ERROR) << __FUNCTION__ << ": PeerConnection is not valid";
+ return;
+ }
+ DCHECK(native_peer_connection_.get());
+ native_peer_connection_->SignalingMessage(UTF16ToUTF8(message));
+}
+
+void MediaStreamImpl::message(
+ const WebKit::WebPeerConnection& web_peer_connection,
+ const WebKit::WebString& message) {
+ if (web_peer_connection != web_peer_connection_) {
+ DLOG(ERROR) << __FUNCTION__ << ": PeerConnection is not valid";
tommi (sloooow) - chröme 2011/10/25 09:58:34 NOTREACHED() instead of DLOG(ERROR)? (same for oth
Henrik Grunell 2011/10/25 12:14:32 Agree, changed.
+ return;
+ }
+ // TODO(grunell): Implement.
+}
-MediaStreamImpl::MediaStreamImpl(VideoCaptureImplManager* vc_manager)
- : vc_manager_(vc_manager) {
+void MediaStreamImpl::addStream(
+ const WebKit::WebPeerConnection& web_peer_connection,
+ const WebKit::WebMediaStream& stream) {
+ if (web_peer_connection != web_peer_connection_) {
+ DLOG(ERROR) << __FUNCTION__ << ": PeerConnection is not valid";
+ return;
+ }
+ // TODO(grunell): Fix code in this function after a new native PeerConnection
+ // version has been rolled out.
+ std::string label = UTF16ToUTF8(stream.label());
+ if (call_state_ == NOT_STARTED) {
+ DCHECK(native_peer_connection_.get());
+ // TODO(grunell): Add audio and/or video depending on what's enabled
+ // in the stream.
+ std::string audioLabel = label;
+ audioLabel.append("-audio");
+ native_peer_connection_->AddStream(audioLabel, false); // Audio
+ native_peer_connection_->AddStream(label, true); // Video
+ call_state_ = INITIATING;
+ }
+ if (call_state_ == INITIATING || call_state_ == RECEIVING) {
+ local_label_ = label;
+ if (!vcm_created_) {
+ // Set the capture device
+ // TODO(grunell): Instead of using the first track, the selected track
+ // should be used.
+ int id = media_stream_dispatcher_->video_session_id(local_label_, 0);
+ if (id != media_stream::StreamDeviceInfo::kNoId) {
+ webrtc::VideoCaptureModule* vcm =
+ new VideoCaptureModuleImpl(id, vc_manager_.get());
+ vcm_created_ = true;
+ media_engine_->SetVideoCaptureModule(vcm);
+ native_peer_connection_->SetVideoCapture("");
+ }
+ }
+ if (call_state_ == INITIATING)
+ native_peer_connection_->Connect();
+ else if (call_state_ == RECEIVING)
+ call_state_ = SENDING_AND_RECEIVING;
+ } else {
+ DLOG(ERROR) << __FUNCTION__ << ": Multiple calls not supported";
+ return;
tommi (sloooow) - chröme 2011/10/25 09:58:34 no need for this return
Henrik Grunell 2011/10/25 12:14:32 Done.
+ }
}
-MediaStreamImpl::~MediaStreamImpl() {}
+void MediaStreamImpl::newPeerConnection(
+ const WebKit::WebPeerConnection& web_peer_connection,
+ const WebKit::WebString& configuration) {
+ if (native_peer_connection_.get()) {
+ LOG(WARNING) << __FUNCTION__ << ": A PeerConnection already exists";
+ return;
+ }
+
+ if (!media_engine_) {
+ media_engine_ = dependency_factory_->CreateWebRtcMediaEngine();
+ }
+
+ if (!signaling_thread_) {
+ jingle_glue::JingleThreadWrapper::EnsureForCurrentThread();
+ jingle_glue::JingleThreadWrapper::current()->set_send_allowed(true);
+ signaling_thread_ = jingle_glue::JingleThreadWrapper::current();
+ }
+
+ if (!worker_thread_) {
+ if (!chrome_worker_thread_.IsRunning()) {
+ if (!chrome_worker_thread_.Start()) {
+ LOG(ERROR) << __FUNCTION__ << ": Could not start worker thread";
+ return;
+ }
+ }
+ base::WaitableEvent event(true, false);
+ chrome_worker_thread_.message_loop()->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this,
+ &MediaStreamImpl::InitializeWorkerThread,
+ &worker_thread_,
+ &event));
+ event.Wait();
+ DCHECK(worker_thread_);
+ }
+
+ if (!dependency_factory_->PeerConnectionFactoryCreated()) {
+ ipc_network_manager_.reset(
+ new content::IpcNetworkManager(p2p_socket_dispatcher_));
+ ipc_socket_factory_.reset(
+ new content::IpcPacketSocketFactory(p2p_socket_dispatcher_));
+ cricket::HttpPortAllocator* port_allocator =
+ new cricket::HttpPortAllocator(ipc_network_manager_.get(),
+ ipc_socket_factory_.get(),
+ "PeerConnection");
+ // TODO(mallinath): The following flags were added to solve a crash in
+ // HttpClient, we should probably remove them after that issue has been
+ // investigated.
+ port_allocator->set_flags(cricket::PORTALLOCATOR_DISABLE_TCP |
+ cricket::PORTALLOCATOR_DISABLE_RELAY);
+
+ // TODO(mallinath): PeerConnectionFactory constructor changed in latest
+ // code and it no more accepts config string. Config string must be parsed
+ // here and set in HttpPortAllocator. Now using standard google STUN server
+ // address.
+ std::vector<talk_base::SocketAddress> stun_hosts;
+ stun_hosts.push_back(talk_base::SocketAddress("stun.l.google.com", 19302));
+ port_allocator->SetStunHosts(stun_hosts);
+
+ if (!dependency_factory_->CreatePeerConnectionFactory(
+ port_allocator,
+ media_engine_,
+ worker_thread_)) {
+ LOG(ERROR) << __FUNCTION__
+ << ": Could not initialize PeerConnection factory";
+ return;
tommi (sloooow) - chröme 2011/10/25 09:58:34 no need for this return statement
Henrik Grunell 2011/10/25 12:14:32 Kept, code below should not be executed.
+ }
+ }
+
+ native_peer_connection_.reset(dependency_factory_->CreatePeerConnection(
+ signaling_thread_));
+ DCHECK(native_peer_connection_.get());
+ web_peer_connection_ = web_peer_connection;
+ native_peer_connection_->RegisterObserver(this);
+}
+
+void MediaStreamImpl::closePeerConnection(
+ const WebKit::WebPeerConnection& web_peer_connection) {
+ if (web_peer_connection != web_peer_connection_) {
+ DLOG(ERROR) << __FUNCTION__ << ": PeerConnection is not valid";
+ return;
+ }
+ if (native_peer_connection_.get())
tommi (sloooow) - chröme 2011/10/25 09:58:34 did you forget {}?
Henrik Grunell 2011/10/25 12:14:32 No {}. Decreased indent below.
+ native_peer_connection_->RegisterObserver(NULL);
+ native_peer_connection_.reset();
+ web_peer_connection_.reset();
+ rtc_video_decoder_ = NULL;
+ media_engine_->SetVideoCaptureModule(NULL);
+ vcm_created_ = false;
+ call_state_ = NOT_STARTED;
+}
+
+void MediaStreamImpl::startNegotiation(
+ const WebKit::WebPeerConnection& web_peer_connection) {
+ if (web_peer_connection != web_peer_connection_) {
+ DLOG(ERROR) << __FUNCTION__ << ": PeerConnection is not valid";
+ return;
+ }
+ // TODO(grunell): Implement. Currently not supported in native PeerConnection.
+}
+
+void MediaStreamImpl::removeStream(
+ const WebKit::WebPeerConnection& web_peer_connection,
+ const WebKit::WebMediaStream& stream) {
+ if (web_peer_connection != web_peer_connection_) {
+ DLOG(ERROR) << __FUNCTION__ << ": PeerConnection is not valid";
+ return;
+ }
+ // TODO(grunell): Implement. Currently not supported in native PeerConnection.
+}
+
+void MediaStreamImpl::commitStreamChanges(
+ const WebKit::WebPeerConnection& web_peer_connection) {
+ if (web_peer_connection != web_peer_connection_) {
+ DLOG(ERROR) << __FUNCTION__ << ": PeerConnection is not valid";
+ return;
+ }
+ // TODO(grunell): Implement. Currently not supported in native PeerConnection.
+}
scoped_refptr<media::VideoDecoder> MediaStreamImpl::GetVideoDecoder(
- const GURL& url, media::MessageLoopFactory* message_loop_factory) {
- bool raw_media = (url.spec().find(kRawMediaScheme) == 0);
- media::VideoDecoder* decoder = NULL;
- if (raw_media) {
+ const GURL& url,
+ media::MessageLoopFactory* message_loop_factory) {
+ std::string label =
+ UTF16ToUTF8(WebKit::WebMediaStreamRegistry::mediaStreamLabel(url));
+ if (label.empty())
+ return NULL; // This is not a valid stream.
+
+ scoped_refptr<media::VideoDecoder> decoder;
+ if (media_stream_dispatcher_->IsStream(label)) {
+ // It's a local stream.
+ int video_session_id = media_stream_dispatcher_->video_session_id(label, 0);
media::VideoCapture::VideoCaptureCapability capability;
capability.width = kVideoCaptureWidth;
capability.height = kVideoCaptureHeight;
@@ -43,10 +347,174 @@ scoped_refptr<media::VideoDecoder> MediaStreamImpl::GetVideoDecoder(
capability.raw_type = media::VideoFrame::I420;
capability.interlaced = false;
capability.resolution_fixed = false;
-
decoder = new CaptureVideoDecoder(
- message_loop_factory->GetMessageLoopProxy("CaptureVideoDecoder").get(),
- kStartOpenSessionId, vc_manager_.get(), capability);
+ message_loop_factory->GetMessageLoopProxy("CaptureVideoDecoderThread"),
+ video_session_id,
+ vc_manager_.get(),
+ capability);
+ } else {
+ // It's a remote stream.
+ size_t found = label.rfind("-remote");
+ if (found != std::string::npos)
+ label = label.substr(0, found);
+ if (rtc_video_decoder_.get()) {
+ // The renderer is used by PeerConnection, release it first.
+ if (native_peer_connection_.get())
+ native_peer_connection_->SetVideoRenderer(label, NULL);
+ }
+ rtc_video_decoder_ = new RTCVideoDecoder(
+ message_loop_factory->GetMessageLoop("RtcVideoDecoderThread"),
+ url.spec());
+ decoder = rtc_video_decoder_;
+ if (native_peer_connection_.get())
+ native_peer_connection_->SetVideoRenderer(label, rtc_video_decoder_);
}
return decoder;
}
+
+void MediaStreamImpl::OnStreamGenerated(
+ int request_id,
+ const std::string& label,
+ const media_stream::StreamDeviceInfoArray& audio_array,
+ const media_stream::StreamDeviceInfoArray& video_array) {
+ DCHECK(controller_.get());
+
+ WebKit::WebVector<WebKit::WebMediaStreamTrack> web_track_vector(
+ audio_array.size() + video_array.size());
+
+ WebKit::WebString track_id(WebKit::WebString::fromUTF8(""));
+ WebKit::WebString track_kind(WebKit::WebString::fromUTF8("main"));
+ WebKit::WebString track_label_audio(
+ WebKit::WebString::fromUTF8("AudioDevice"));
+ WebKit::WebString track_label_video(
+ WebKit::WebString::fromUTF8("VideoCapture"));
+ unsigned int track_num = web_track_vector.size();
tommi (sloooow) - chröme 2011/10/25 09:58:34 size_t?
Henrik Grunell 2011/10/25 12:14:32 Done.
+ while (track_num--) {
+ if (track_num < audio_array.size()) {
+ web_track_vector[track_num].initialize(track_id,
+ track_kind,
+ track_label_audio);
+ } else {
+ web_track_vector[track_num].initialize(track_id,
+ track_kind,
+ track_label_video);
+ }
+ }
+
+ WebKit::WebMediaStreamTrackList web_track_list;
+ web_track_list.initialize(web_track_vector);
+
+ WebKit::WebLocalMediaStream web_local_media_stream;
+ web_local_media_stream.initialize(UTF8ToUTF16(label), web_track_list);
+
+ controller_->streamGenerated(request_id, web_local_media_stream);
+}
+
+void MediaStreamImpl::OnStreamGenerationFailed(int request_id) {
+ VLOG(1) << "MediaStreamImpl::OnStreamGenerationFailed("
+ << request_id << ")";
+ DCHECK(controller_.get());
+ controller_->streamGenerationFailed(
+ request_id,
+ WebKit::WebMediaStreamController::ErrorPermissionDenied);
+}
+
+void MediaStreamImpl::OnVideoDeviceFailed(const std::string& label,
+ int index) {
+ VLOG(1) << "MediaStreamImpl::OnVideoDeviceFailed("
+ << label << ", " << index << ")";
+ DCHECK(controller_.get());
+ controller_->streamFailed(UTF8ToUTF16(label));
+}
+
+void MediaStreamImpl::OnAudioDeviceFailed(const std::string& label,
+ int index) {
+ VLOG(1) << "MediaStreamImpl::OnAudioDeviceFailed("
+ << label << ", " << index << ")";
+ DCHECK(controller_.get());
+ controller_->streamFailed(UTF8ToUTF16(label));
+}
+
+void MediaStreamImpl::OnSignalingMessage(const std::string& msg) {
+ if (!message_loop_proxy_->BelongsToCurrentThread()) {
+ message_loop_proxy_->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this, &MediaStreamImpl::OnSignalingMessage, msg));
+ return;
+ }
+
+ DCHECK(controller_.get());
+ controller_->onSignalingMessage(web_peer_connection_, UTF8ToUTF16(msg));
+}
+
+void MediaStreamImpl::OnAddStream(const std::string& stream_id, bool video) {
+ // TODO(grunell): Fix code in this function after a new native PeerConnection
+ // version has been rolled out.
+ if (video) {
+ if (call_state_ == NOT_STARTED) {
+ remote_label_ = stream_id;
+ call_state_ = RECEIVING;
+ } else if (call_state_ == INITIATING) {
+ remote_label_ = local_label_;
+ remote_label_ += "-remote";
+ call_state_ = SENDING_AND_RECEIVING;
+ }
+
+ if (!message_loop_proxy_->BelongsToCurrentThread()) {
+ message_loop_proxy_->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this,
+ &MediaStreamImpl::OnAddStreamCallback,
+ remote_label_));
+ return;
tommi (sloooow) - chröme 2011/10/25 09:58:34 no need for return
Henrik Grunell 2011/10/25 12:14:32 Done.
+ } else {
+ OnAddStreamCallback(remote_label_);
+ }
+ }
+}
+
+void MediaStreamImpl::OnRemoveStream(const std::string& stream_id, bool video) {
+ if (video) {
+ if (!message_loop_proxy_->BelongsToCurrentThread()) {
+ message_loop_proxy_->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this,
+ &MediaStreamImpl::OnRemoveStreamCallback,
+ remote_label_));
+ return;
tommi (sloooow) - chröme 2011/10/25 09:58:34 no need for return
Henrik Grunell 2011/10/25 12:14:32 Done.
+ } else {
+ OnRemoveStreamCallback(remote_label_);
+ }
+ }
+}
+
+void MediaStreamImpl::OnAddStreamCallback(const std::string& stream_label) {
+ DCHECK(controller_.get());
+
+ // Currently only support for one track
+ size_t size = 1;
+ WebKit::WebVector<WebKit::WebMediaStreamTrack> web_track_vector(size);
+ web_track_vector[0].initialize(WebKit::WebString::fromUTF8(""),
+ WebKit::WebString::fromUTF8("main"),
+ WebKit::WebString::fromUTF8(stream_label));
+ WebKit::WebMediaStreamTrackList web_track_list;
+ web_track_list.initialize(web_track_vector);
+
+ WebKit::WebMediaStream web_media_stream;
+ web_media_stream.initialize(UTF8ToUTF16(stream_label), web_track_list);
+
+ controller_->onAddStream(web_peer_connection_, web_media_stream);
+}
+
+void MediaStreamImpl::OnRemoveStreamCallback(const std::string& stream_label) {
+ DCHECK(controller_.get());
+ controller_->onRemoveStream(web_peer_connection_, UTF8ToUTF16(stream_label));
+}
+
+void MediaStreamImpl::InitializeWorkerThread(talk_base::Thread** thread,
+ base::WaitableEvent* event) {
+ jingle_glue::JingleThreadWrapper::EnsureForCurrentThread();
+ jingle_glue::JingleThreadWrapper::current()->set_send_allowed(true);
+ *thread = jingle_glue::JingleThreadWrapper::current();
+ event->Signal();
+}

Powered by Google App Engine
This is Rietveld 408576698